1 module dpq2.query;
2 
3 @trusted:
4 
5 public import dpq2.connection;
6 import dpq2.answer: Notify;
7 
8 enum ValueFormat : ubyte {
9     TEXT,
10     BINARY
11 }
12 
13 /// Query parameters
14 struct QueryParams
15 {
16     string sqlCommand; /// SQL command
17     QueryArg[] args; /// SQL command arguments
18     ValueFormat resultFormat = ValueFormat.BINARY; /// Result value format
19 }
20 
21 /// Query argument
22 struct QueryArg
23 {
24     Oid type = 0;
25     private ubyte[] valueBin;
26     
27     /// s can be null for SQL NULL value
28     @property void value( string s )
29     {
30         if( s == null )
31             valueBin = null;
32         else
33             valueBin = cast(ubyte[])( s ~ '\0' );
34     }
35 }
36 
37 /// Connection
38 // Inheritance used here for separation of query code from connection code
39 final class Connection: BaseConnection
40 {
41     /// Perform SQL query to DB
42     immutable (Answer) exec( string SQLcmd )
43     {
44         auto r = getAnswer(
45             PQexec(conn, toStringz( SQLcmd ))
46         );
47         
48         return r;
49     }
50     
51     /// Perform SQL query to DB
52     immutable (Answer) exec(ref const QueryParams p)
53     {
54         auto a = prepareArgs( p );
55         auto r = getAnswer
56         (
57             PQexecParams (
58                 conn,
59                 cast(const char*)toStringz( p.sqlCommand ),
60                 cast(int)p.args.length,
61                 a.types.ptr,
62                 a.values.ptr,
63                 cast(int*)a.lengths.ptr,
64                 cast(int*)a.formats.ptr,
65                 cast(int)p.resultFormat
66             )
67         );
68         
69         return r;
70     }
71     
72     /// Submits a command to the server without waiting for the result(s)
73     void sendQuery( string SQLcmd )
74     {
75         const size_t r = PQsendQuery( conn, toStringz(SQLcmd) );
76         if( r != 1 ) throw new ConnException(this, __FILE__, __LINE__);
77     }
78     
79     /// Submits a command and separate parameters to the server without waiting for the result(s)
80     void sendQuery( ref const QueryParams p )
81     {
82         auto a = prepareArgs( p );
83         size_t r = PQsendQueryParams (
84                     conn,
85                     cast(const char*)toStringz( p.sqlCommand ),
86                     cast(int)p.args.length,
87                     a.types.ptr,
88                     a.values.ptr,
89                     cast(int*)a.lengths.ptr,
90                     cast(int*)a.formats.ptr,
91                     cast(int)p.resultFormat                    
92                     );
93                     
94         if( !r ) throw new ConnException(this, __FILE__, __LINE__);
95     }
96     
97     /// getResult would block waiting for input?
98     package bool isBusy()
99     {
100         return PQisBusy(conn) == 1;
101     }
102     
103     /// Returns null if no notifies was received
104     Notify getNextNotify()
105     {
106         consumeInput();
107         auto n = PQnotifies(conn);
108         return n is null ? null : new Notify( n );
109     }
110     
111     private struct PreparedArgs
112     {
113         Oid[] types;
114         size_t[] formats;
115         size_t[] lengths;
116         const(ubyte)*[] values;
117     }
118     
119     // For PQxxxParams need especially prepared arguments
120     private PreparedArgs* prepareArgs(ref const QueryParams p)
121     {
122         PreparedArgs* a = new PreparedArgs;
123         a.types = new Oid[p.args.length];
124         a.formats = new size_t[p.args.length];
125         a.lengths = new size_t[p.args.length];
126         a.values = new const(ubyte)*[p.args.length];
127         
128         for( int i = 0; i < p.args.length; ++i )
129         {
130             a.types[i] = p.args[i].type;
131             a.formats[i] = ValueFormat.TEXT;
132             a.values[i] = p.args[i].valueBin.ptr;
133             a.lengths[i] = p.args[i].valueBin.length;
134         }
135         
136         return a;
137     }
138 }
139 
140 void _integration_test( string connParam )
141 {
142     auto conn = new Connection;
143 	conn.connString = connParam;
144     conn.connect();
145 
146     {    
147         string sql_query =
148         "select now() as time, 'abc'::text as string, 123, 456.78\n"~
149         "union all\n"~
150         "select now(), 'абвгд'::text, 777, 910.11\n"~
151         "union all\n"~
152         "select NULL, 'ijk'::text, 789, 12345.115345";
153 
154         auto a = conn.exec( sql_query );
155 
156         assert( a.cmdStatus.length > 2 );
157         assert( a.columnCount == 4 );
158         assert( a.length == 3 );
159         assert( a.columnFormat(1) == ValueFormat.TEXT );
160         assert( a.columnFormat(2) == ValueFormat.TEXT );
161     }
162 
163     {
164         const string sql_query =
165         "select $1::text, $2::integer, $3::text";
166 
167         QueryArg[3] args;
168         args[0].value = "абвгд";
169         args[1].value = null;
170         args[2].value = "123";
171 
172         QueryParams p;
173         p.sqlCommand = sql_query;
174         p.args = args[];
175 
176         auto a = conn.exec( p );
177 
178         assert( a.columnFormat(1) == ValueFormat.BINARY );
179         assert( a.columnFormat(2) == ValueFormat.BINARY );
180 
181         destroy(a);
182     }
183 
184     conn.disconnect();
185 
186     {
187         bool exceptionFlag = false;
188 
189         try conn.exec("SELECT 'abc'::text");
190         catch(ConnException e)
191         {
192             exceptionFlag = true;
193             assert(e.msg.length > 15); // error message check
194         }
195         finally
196             assert(exceptionFlag);
197     }
198 }