1
/// Generates SQL query with appropriate variable types
2
module dpq2.query_gen;
3

4
import dpq2.args: QueryParams;
5
import dpq2.connection: Connection;
6
import std.conv: to;
7
import std.traits: isInstanceOf;
8
import std.array: appender;
9
import dpq2.conv.from_d_types: toValue;
10

11
private enum ArgLikeIn
12
{
13
    INSERT, // looks like "FieldName" and passes value as appropriate variable
14
    UPDATE, // looks like "FieldName" = $3 and passes value into appropriate dollar variable
15
}
16

17
private struct Arg(ArgLikeIn _argLikeIn, string _name, T)
18
{
19
    enum argLikeIn = _argLikeIn;
20
    enum name = _name;
21

22
    T value;
23
}
24

25
private struct DollarArg(T)
26
{
27
    T value;
28
}
29

30
/// INSERT-like argument
31
auto i(string statementArgName, T)(T value)
32
{
33 1
    return Arg!(ArgLikeIn.INSERT, statementArgName, T)(value);
34
}
35

36
/// UPDATE-like argument
37
auto u(string statementArgName, T)(T value)
38
{
39 1
    return Arg!(ArgLikeIn.UPDATE, statementArgName, T)(value);
40
}
41

42
/// Argument representing dollar, usable in SELECT statements
43
auto d(T)(T value)
44
{
45 1
    return DollarArg!T(value);
46
}
47

48
private struct CTStatement(SQL_CMD...)
49
{
50
    QueryParams qp;
51

52 1
    this(SQL_CMD sqlCmd, Connection conn)
53
    {
54 1
        qp = parseSqlCmd!SQL_CMD(sqlCmd, conn);
55
    }
56
}
57

58
private string dollarsString(size_t num)
59
{
60 1
    string ret;
61

62 1
    foreach(i; 1 .. num+1)
63
    {
64 1
          ret ~= `$`;
65 1
          ret ~= i.to!string;
66

67 1
          if(i < num)
68 1
                ret ~= `,`;
69
    }
70

71 1
    return ret;
72
}
73

74
private template isStatementArg(T)
75
{
76
    enum isStatementArg = 
77
        isInstanceOf!(Arg, T) ||
78
        isInstanceOf!(DollarArg, T);
79
}
80

81
private bool symbolNeedsDelimit(dchar c)
82
{
83
    import std.ascii: isAlphaNum;
84

85 1
    return c == '$' || c.isAlphaNum;
86
}
87

88
private void concatWithDelimiter(A, T)(ref A appender, T val)
89
{
90

91 1
    if(
92 1
        val.length && val[0].symbolNeedsDelimit &&
93 1
        appender.data.length && appender.data[$-1].symbolNeedsDelimit
94
    )
95 1
        appender ~= ' ';
96

97 1
    appender ~= val;
98
}
99

100
private string escapeName(string s, Connection conn)
101
{
102 1
    if(conn !is null)
103 0
        return conn.escapeIdentifier(s);
104
    else
105 1
        return '"'~s~'"';
106
}
107

108
private QueryParams parseSqlCmd(SQL_CMD...)(SQL_CMD sqlCmd, Connection conn)
109
{
110 1
    QueryParams qp;
111 1
    auto resultSql = appender!string;
112

113 1
    foreach(i, V; sqlCmd)
114
    {
115
        // argument variable is found?
116
        static if(isStatementArg!(typeof(V)))
117
        {
118
            // previous argument already was processed?
119
            static if(isStatementArg!(typeof(sqlCmd[i-1])))
120
            {
121 1
                resultSql ~= `,`;
122
            }
123

124
            static if(isInstanceOf!(DollarArg, typeof(V)))
125
            {
126 1
                resultSql ~= `$`;
127 1
                resultSql ~= (qp.args.length + 1).to!string;
128
            }
129
            else static if(V.argLikeIn == ArgLikeIn.UPDATE)
130
            {
131 1
                resultSql ~= V.name.escapeName(conn);
132 1
                resultSql ~= `=$`;
133 1
                resultSql ~= (qp.args.length + 1).to!string;
134
            }
135
            else static if(V.argLikeIn == ArgLikeIn.INSERT)
136
            {
137 1
                resultSql ~= V.name.escapeName(conn);
138
            }
139
            else
140
                static assert(false);
141

142 1
            qp.args ~= V.value.toValue;
143
        }
144
        else
145
        {
146
            // Usable as INSERT VALUES ($1, $2, ...) argument
147
            static if(is(typeof(V) == Dollars))
148
            {
149 1
                resultSql ~= dollarsString(qp.args.length);
150
            }
151
            else
152
            {
153
                // ordinary part of SQL statement
154 1
                resultSql.concatWithDelimiter(V);
155
            }
156
        }
157
    }
158

159 1
    qp.sqlCommand = resultSql[];
160

161 1
    return qp;
162
}
163

164
struct Dollars {}
165

166
auto wrapStatement(T...)(T statement, Connection conn = null) {
167 1
    return CTStatement!T(statement, conn);
168
}
169

170
unittest
171
{
172 1
    auto stmnt = wrapStatement(`abc=`, d(123));
173

174 1
    assert(stmnt.qp.sqlCommand == `abc=$1`);
175 1
    assert(stmnt.qp.args.length == 1);
176 1
    assert(stmnt.qp.args[0] == 123.toValue);
177
}
178

179
unittest
180
{
181 1
    auto stmnt = wrapStatement(
182
        `SELECT`, d!string("abc"), d!int(123)
183
    );
184

185 1
    assert(stmnt.qp.args.length == 2);
186 1
    assert(stmnt.qp.args[0] == "abc".toValue);
187 1
    assert(stmnt.qp.args[1] == 123.toValue);
188
}
189

190
unittest
191
{
192 1
    auto stmnt = wrapStatement(
193
        `UPDATE table1`,
194
        `SET`,
195
            u!`boolean_field`(true),
196
            u!`integer_field`(123),
197
            u!`text_field`(`abc`),
198
    );
199

200 1
    assert(stmnt.qp.sqlCommand.length > 10);
201 1
    assert(stmnt.qp.args.length == 3);
202 1
    assert(stmnt.qp.args[0] == true.toValue);
203 1
    assert(stmnt.qp.args[1] == 123.toValue);
204 1
    assert(stmnt.qp.args[2] == `abc`.toValue);
205
}
206

207
unittest
208
{
209 1
    int integer = 123;
210 1
    int another_integer = 456;
211 1
    string text = "abc";
212

213 1
    auto stmnt = wrapStatement(
214
        `INSERT INTO table1 (`,
215
            i!`integer_field`(integer),
216
            i!`text_field`(text),
217
        `) WHERE`,
218
            u!`integer_field`(another_integer),
219
        `VALUES(`, Dollars(),`)`
220
    );
221

222 1
    assert(stmnt.qp.sqlCommand.length > 10);
223 1
    assert(stmnt.qp.args[0] == 123.toValue);
224 1
    assert(stmnt.qp.args[1] == `abc`.toValue);
225 1
    assert(stmnt.qp.args[2] == 456.toValue);
226
}

Read our documentation on viewing source code .

Loading