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 }