├── .editorconfig ├── .gitignore ├── LICENSE ├── bindings ├── unix.jai └── windows.jai ├── byte_order.jai ├── examples ├── example.jai └── first.jai ├── first.jai ├── generate.jai ├── generate_types.jai ├── module.jai ├── pq_types.jai └── readme.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | 11 | # We recommend you to keep these unchanged 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | pgtest 3 | examples/example 4 | *.dSYM 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Raphael Luba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bindings/unix.jai: -------------------------------------------------------------------------------- 1 | // 2 | // This file was auto-generated using the following command: 3 | // 4 | // jai generate.jai 5 | // 6 | 7 | 8 | 9 | InvalidOid :: cast(Oid) 0; 10 | 11 | PG_COPYRES_ATTRS :: 0x01; 12 | PG_COPYRES_TUPLES :: 0x02; 13 | PG_COPYRES_EVENTS :: 0x04; 14 | PG_COPYRES_NOTICEHOOKS :: 0x08; 15 | 16 | PQnoPasswordSupplied :: "fe_sendauth: no password supplied\n"; 17 | 18 | /* 19 | * Object ID is a fundamental type in Postgres. 20 | */ 21 | Oid :: u32; 22 | 23 | /* Define a signed 64-bit integer type for use in client API declarations. */ 24 | pg_int64 :: s64; 25 | 26 | /* 27 | * Although it is okay to add to these lists, values which become unused 28 | * should never be removed, nor should constants be redefined - that would 29 | * break compatibility with existing code. 30 | */ 31 | ConnStatusType :: enum u32 { 32 | CONNECTION_OK :: 0; 33 | CONNECTION_BAD :: 1; 34 | 35 | CONNECTION_STARTED :: 2; 36 | CONNECTION_MADE :: 3; 37 | CONNECTION_AWAITING_RESPONSE :: 4; 38 | 39 | CONNECTION_AUTH_OK :: 5; 40 | 41 | CONNECTION_SETENV :: 6; 42 | CONNECTION_SSL_STARTUP :: 7; 43 | CONNECTION_NEEDED :: 8; 44 | CONNECTION_CHECK_WRITABLE :: 9; 45 | 46 | CONNECTION_CONSUME :: 10; 47 | } 48 | 49 | PostgresPollingStatusType :: enum u32 { 50 | PGRES_POLLING_FAILED :: 0; 51 | PGRES_POLLING_READING :: 1; 52 | PGRES_POLLING_WRITING :: 2; 53 | PGRES_POLLING_OK :: 3; 54 | PGRES_POLLING_ACTIVE :: 4; 55 | } 56 | 57 | ExecStatusType :: enum u32 { 58 | PGRES_EMPTY_QUERY :: 0; 59 | PGRES_COMMAND_OK :: 1; 60 | 61 | PGRES_TUPLES_OK :: 2; 62 | 63 | PGRES_COPY_OUT :: 3; 64 | PGRES_COPY_IN :: 4; 65 | PGRES_BAD_RESPONSE :: 5; 66 | 67 | PGRES_NONFATAL_ERROR :: 6; 68 | PGRES_FATAL_ERROR :: 7; 69 | PGRES_COPY_BOTH :: 8; 70 | PGRES_SINGLE_TUPLE :: 9; 71 | } 72 | 73 | PGTransactionStatusType :: enum u32 { 74 | PQTRANS_IDLE :: 0; 75 | PQTRANS_ACTIVE :: 1; 76 | PQTRANS_INTRANS :: 2; 77 | PQTRANS_INERROR :: 3; 78 | PQTRANS_UNKNOWN :: 4; 79 | } 80 | 81 | PGVerbosity :: enum u32 { 82 | PQERRORS_TERSE :: 0; 83 | PQERRORS_DEFAULT :: 1; 84 | PQERRORS_VERBOSE :: 2; 85 | } 86 | 87 | PGContextVisibility :: enum u32 { 88 | PQSHOW_CONTEXT_NEVER :: 0; 89 | PQSHOW_CONTEXT_ERRORS :: 1; 90 | PQSHOW_CONTEXT_ALWAYS :: 2; 91 | } 92 | 93 | /* 94 | * PGPing - The ordering of this enum should not be altered because the 95 | * values are exposed externally via pg_isready. 96 | */ 97 | PGPing :: enum u32 { 98 | PQPING_OK :: 0; 99 | PQPING_REJECT :: 1; 100 | PQPING_NO_RESPONSE :: 2; 101 | PQPING_NO_ATTEMPT :: 3; 102 | } 103 | 104 | pg_conn :: struct {} 105 | /* PGconn encapsulates a connection to the backend. 106 | * The contents of this struct are not supposed to be known to applications. 107 | */ 108 | PGconn :: pg_conn; 109 | 110 | pg_result :: struct {} 111 | /* PGresult encapsulates the result of a query (or more precisely, of a single 112 | * SQL command --- a query string given to PQsendQuery can contain multiple 113 | * commands and thus return multiple PGresult objects). 114 | * The contents of this struct are not supposed to be known to applications. 115 | */ 116 | PGresult :: pg_result; 117 | 118 | pg_cancel :: struct {} 119 | /* PGcancel encapsulates the information needed to cancel a running 120 | * query on an existing connection. 121 | * The contents of this struct are not supposed to be known to applications. 122 | */ 123 | PGcancel :: pg_cancel; 124 | 125 | /* PGnotify represents the occurrence of a NOTIFY message. 126 | * Ideally this would be an opaque typedef, but it's so simple that it's 127 | * unlikely to change. 128 | * NOTE: in Postgres 6.4 and later, the be_pid is the notifying backend's, 129 | * whereas in earlier versions it was always your own backend's PID. 130 | */ 131 | pgNotify :: struct { 132 | relname: *u8; /* notification condition name */ 133 | be_pid: s32; /* process ID of notifying server process */ 134 | extra: *u8; /* notification parameter */ 135 | 136 | next: *pgNotify; /* list link */ 137 | } 138 | 139 | /* PGnotify represents the occurrence of a NOTIFY message. 140 | * Ideally this would be an opaque typedef, but it's so simple that it's 141 | * unlikely to change. 142 | * NOTE: in Postgres 6.4 and later, the be_pid is the notifying backend's, 143 | * whereas in earlier versions it was always your own backend's PID. 144 | */ 145 | PGnotify :: pgNotify; 146 | 147 | /* Function types for notice-handling callbacks */ 148 | PQnoticeReceiver :: #type (arg: *void, res: *PGresult) -> void #c_call; 149 | PQnoticeProcessor :: #type (arg: *void, message: *u8) -> void #c_call; 150 | 151 | /* Print options for PQprint() */ 152 | pqbool :: u8; 153 | 154 | _PQprintOpt :: struct { 155 | header: pqbool; /* print output field headings and row count */ 156 | align: pqbool; /* fill align the fields */ 157 | standard: pqbool; /* old brain dead format */ 158 | html3: pqbool; /* output html tables */ 159 | expanded: pqbool; /* expand tables */ 160 | pager: pqbool; /* use pager for output if needed */ 161 | fieldSep: *u8; /* field separator */ 162 | tableOpt: *u8; /* insert to HTML */ 163 | caption: *u8; /* HTML
*/ 164 | /* null terminated array of replacement field 165 | * names */ 166 | fieldName: **u8; 167 | } 168 | 169 | PQprintOpt :: _PQprintOpt; 170 | 171 | /* ---------------- 172 | * Structure for the conninfo parameter definitions returned by PQconndefaults 173 | * or PQconninfoParse. 174 | * 175 | * All fields except "val" point at static strings which must not be altered. 176 | * "val" is either NULL or a malloc'd current-value string. PQconninfoFree() 177 | * will release both the val strings and the PQconninfoOption array itself. 178 | * ---------------- 179 | */ 180 | _PQconninfoOption :: struct { 181 | keyword: *u8; /* The keyword of the option */ 182 | envvar: *u8; /* Fallback environment variable name */ 183 | compiled: *u8; /* Fallback compiled in default value */ 184 | val: *u8; /* Option's current value, or NULL */ 185 | label: *u8; /* Label for field in connect dialog */ 186 | /* Indicates how to display this field in a 187 | * connect dialog. Values are: "" Display 188 | * entered value as is "*" Password field - 189 | * hide value "D" Debug option - don't show 190 | * by default */ 191 | dispchar: *u8; 192 | 193 | dispsize: s32; /* Field size in characters for dialog */ 194 | } 195 | 196 | /* ---------------- 197 | * Structure for the conninfo parameter definitions returned by PQconndefaults 198 | * or PQconninfoParse. 199 | * 200 | * All fields except "val" point at static strings which must not be altered. 201 | * "val" is either NULL or a malloc'd current-value string. PQconninfoFree() 202 | * will release both the val strings and the PQconninfoOption array itself. 203 | * ---------------- 204 | */ 205 | PQconninfoOption :: _PQconninfoOption; 206 | 207 | /* ---------------- 208 | * PQArgBlock -- structure for PQfn() arguments 209 | * ---------------- 210 | */ 211 | PQArgBlock :: struct { 212 | len: s32; 213 | isint: s32; 214 | u: union { 215 | ptr: *s32; /* can't use void (dec compiler barfs) */ 216 | integer: s32; 217 | }; 218 | } 219 | 220 | /* ---------------- 221 | * PGresAttDesc -- Data about a single attribute (column) of a query result 222 | * ---------------- 223 | */ 224 | pgresAttDesc :: struct { 225 | name: *u8; /* column name */ 226 | tableid: Oid; /* source table, if known */ 227 | columnid: s32; /* source column, if known */ 228 | format: s32; /* format code for value (text/binary) */ 229 | typid: Oid; /* type id */ 230 | typlen: s32; /* type size */ 231 | atttypmod: s32; /* type-specific modifier info */ 232 | } 233 | 234 | /* ---------------- 235 | * PGresAttDesc -- Data about a single attribute (column) of a query result 236 | * ---------------- 237 | */ 238 | PGresAttDesc :: pgresAttDesc; 239 | 240 | /* make a new client connection to the backend */ 241 | /* Asynchronous (non-blocking) */ 242 | PQconnectStart :: (conninfo: *u8) -> *PGconn #foreign libpq; 243 | PQconnectStartParams :: (keywords: **u8, values: **u8, expand_dbname: s32) -> *PGconn #foreign libpq; 244 | 245 | PQconnectPoll :: (conn: *PGconn) -> PostgresPollingStatusType #foreign libpq; 246 | 247 | /* Synchronous (blocking) */ 248 | PQconnectdb :: (conninfo: *u8) -> *PGconn #foreign libpq; 249 | PQconnectdbParams :: (keywords: **u8, values: **u8, expand_dbname: s32) -> *PGconn #foreign libpq; 250 | 251 | PQsetdbLogin :: (pghost: *u8, pgport: *u8, pgoptions: *u8, pgtty: *u8, dbName: *u8, login: *u8, pwd: *u8) -> *PGconn #foreign libpq; 252 | 253 | /* close the current connection and free the PGconn data structure */ 254 | PQfinish :: (conn: *PGconn) -> void #foreign libpq; 255 | 256 | /* get info about connection options known to PQconnectdb */ 257 | PQconndefaults :: () -> *PQconninfoOption #foreign libpq; 258 | 259 | /* parse connection options in same way as PQconnectdb */ 260 | PQconninfoParse :: (conninfo: *u8, errmsg: **u8) -> *PQconninfoOption #foreign libpq; 261 | 262 | /* return the connection options used by a live connection */ 263 | PQconninfo :: (conn: *PGconn) -> *PQconninfoOption #foreign libpq; 264 | 265 | /* free the data structure returned by PQconndefaults() or PQconninfoParse() */ 266 | PQconninfoFree :: (connOptions: *PQconninfoOption) -> void #foreign libpq; 267 | 268 | /* 269 | * close the current connection and restablish a new one with the same 270 | * parameters 271 | */ 272 | /* Asynchronous (non-blocking) */ 273 | PQresetStart :: (conn: *PGconn) -> s32 #foreign libpq; 274 | PQresetPoll :: (conn: *PGconn) -> PostgresPollingStatusType #foreign libpq; 275 | 276 | /* Synchronous (blocking) */ 277 | PQreset :: (conn: *PGconn) -> void #foreign libpq; 278 | 279 | /* request a cancel structure */ 280 | PQgetCancel :: (conn: *PGconn) -> *PGcancel #foreign libpq; 281 | 282 | /* free a cancel structure */ 283 | PQfreeCancel :: (cancel: *PGcancel) -> void #foreign libpq; 284 | 285 | /* issue a cancel request */ 286 | PQcancel :: (cancel: *PGcancel, errbuf: *u8, errbufsize: s32) -> s32 #foreign libpq; 287 | 288 | /* backwards compatible version of PQcancel; not thread-safe */ 289 | PQrequestCancel :: (conn: *PGconn) -> s32 #foreign libpq; 290 | 291 | /* Accessor functions for PGconn objects */ 292 | PQdb :: (conn: *PGconn) -> *u8 #foreign libpq; 293 | PQuser :: (conn: *PGconn) -> *u8 #foreign libpq; 294 | PQpass :: (conn: *PGconn) -> *u8 #foreign libpq; 295 | PQhost :: (conn: *PGconn) -> *u8 #foreign libpq; 296 | PQport :: (conn: *PGconn) -> *u8 #foreign libpq; 297 | PQtty :: (conn: *PGconn) -> *u8 #foreign libpq; 298 | PQoptions :: (conn: *PGconn) -> *u8 #foreign libpq; 299 | PQstatus :: (conn: *PGconn) -> ConnStatusType #foreign libpq; 300 | PQtransactionStatus :: (conn: *PGconn) -> PGTransactionStatusType #foreign libpq; 301 | PQparameterStatus :: (conn: *PGconn, paramName: *u8) -> *u8 #foreign libpq; 302 | 303 | PQprotocolVersion :: (conn: *PGconn) -> s32 #foreign libpq; 304 | PQserverVersion :: (conn: *PGconn) -> s32 #foreign libpq; 305 | PQerrorMessage :: (conn: *PGconn) -> *u8 #foreign libpq; 306 | PQsocket :: (conn: *PGconn) -> s32 #foreign libpq; 307 | PQbackendPID :: (conn: *PGconn) -> s32 #foreign libpq; 308 | PQconnectionNeedsPassword :: (conn: *PGconn) -> s32 #foreign libpq; 309 | PQconnectionUsedPassword :: (conn: *PGconn) -> s32 #foreign libpq; 310 | PQclientEncoding :: (conn: *PGconn) -> s32 #foreign libpq; 311 | PQsetClientEncoding :: (conn: *PGconn, encoding: *u8) -> s32 #foreign libpq; 312 | 313 | /* SSL information functions */ 314 | PQsslInUse :: (conn: *PGconn) -> s32 #foreign libpq; 315 | PQsslStruct :: (conn: *PGconn, struct_name: *u8) -> *void #foreign libpq; 316 | PQsslAttribute :: (conn: *PGconn, attribute_name: *u8) -> *u8 #foreign libpq; 317 | PQsslAttributeNames :: (conn: *PGconn) -> **u8 #foreign libpq; 318 | 319 | /* Get the OpenSSL structure associated with a connection. Returns NULL for 320 | * unencrypted connections or if any other TLS library is in use. */ 321 | PQgetssl :: (conn: *PGconn) -> *void #foreign libpq; 322 | 323 | /* Tell libpq whether it needs to initialize OpenSSL */ 324 | PQinitSSL :: (do_init: s32) -> void #foreign libpq; 325 | 326 | /* More detailed way to tell libpq whether it needs to initialize OpenSSL */ 327 | PQinitOpenSSL :: (do_ssl: s32, do_crypto: s32) -> void #foreign libpq; 328 | 329 | /* Set verbosity for PQerrorMessage and PQresultErrorMessage */ 330 | PQsetErrorVerbosity :: (conn: *PGconn, verbosity: PGVerbosity) -> PGVerbosity #foreign libpq; 331 | 332 | /* Set CONTEXT visibility for PQerrorMessage and PQresultErrorMessage */ 333 | PQsetErrorContextVisibility :: (conn: *PGconn, show_context: PGContextVisibility) -> PGContextVisibility #foreign libpq; 334 | 335 | /* Enable/disable tracing */ 336 | PQtrace :: (conn: *PGconn, debug_port: *FILE) -> void #foreign libpq; 337 | PQuntrace :: (conn: *PGconn) -> void #foreign libpq; 338 | 339 | /* Override default notice handling routines */ 340 | PQsetNoticeReceiver :: (conn: *PGconn, proc: PQnoticeReceiver, arg: *void) -> PQnoticeReceiver #foreign libpq; 341 | 342 | PQsetNoticeProcessor :: (conn: *PGconn, proc: PQnoticeProcessor, arg: *void) -> PQnoticeProcessor #foreign libpq; 343 | 344 | /* 345 | * Used to set callback that prevents concurrent access to 346 | * non-thread safe functions that libpq needs. 347 | * The default implementation uses a libpq internal mutex. 348 | * Only required for multithreaded apps that use kerberos 349 | * both within their app and for postgresql connections. 350 | */ 351 | pgthreadlock_t :: #type (acquire: s32) -> void #c_call; 352 | 353 | PQregisterThreadLock :: (newhandler: pgthreadlock_t) -> pgthreadlock_t #foreign libpq; 354 | 355 | /* Simple synchronous query */ 356 | PQexec :: (conn: *PGconn, query: *u8) -> *PGresult #foreign libpq; 357 | PQexecParams :: (conn: *PGconn, command: *u8, nParams: s32, paramTypes: *Oid, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> *PGresult #foreign libpq; 358 | 359 | PQprepare :: (conn: *PGconn, stmtName: *u8, query: *u8, nParams: s32, paramTypes: *Oid) -> *PGresult #foreign libpq; 360 | 361 | PQexecPrepared :: (conn: *PGconn, stmtName: *u8, nParams: s32, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> *PGresult #foreign libpq; 362 | 363 | /* Interface for multiple-result or asynchronous queries */ 364 | PQsendQuery :: (conn: *PGconn, query: *u8) -> s32 #foreign libpq; 365 | PQsendQueryParams :: (conn: *PGconn, command: *u8, nParams: s32, paramTypes: *Oid, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> s32 #foreign libpq; 366 | 367 | PQsendPrepare :: (conn: *PGconn, stmtName: *u8, query: *u8, nParams: s32, paramTypes: *Oid) -> s32 #foreign libpq; 368 | 369 | PQsendQueryPrepared :: (conn: *PGconn, stmtName: *u8, nParams: s32, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> s32 #foreign libpq; 370 | 371 | PQsetSingleRowMode :: (conn: *PGconn) -> s32 #foreign libpq; 372 | PQgetResult :: (conn: *PGconn) -> *PGresult #foreign libpq; 373 | 374 | /* Routines for managing an asynchronous query */ 375 | PQisBusy :: (conn: *PGconn) -> s32 #foreign libpq; 376 | PQconsumeInput :: (conn: *PGconn) -> s32 #foreign libpq; 377 | 378 | /* LISTEN/NOTIFY support */ 379 | PQnotifies :: (conn: *PGconn) -> *PGnotify #foreign libpq; 380 | 381 | /* Routines for copy in/out */ 382 | PQputCopyData :: (conn: *PGconn, buffer: *u8, nbytes: s32) -> s32 #foreign libpq; 383 | PQputCopyEnd :: (conn: *PGconn, errormsg: *u8) -> s32 #foreign libpq; 384 | PQgetCopyData :: (conn: *PGconn, buffer: **u8, async: s32) -> s32 #foreign libpq; 385 | 386 | /* Deprecated routines for copy in/out */ 387 | PQgetline :: (conn: *PGconn, _string: *u8, length: s32) -> s32 #foreign libpq; 388 | PQputline :: (conn: *PGconn, _string: *u8) -> s32 #foreign libpq; 389 | PQgetlineAsync :: (conn: *PGconn, buffer: *u8, bufsize: s32) -> s32 #foreign libpq; 390 | PQputnbytes :: (conn: *PGconn, buffer: *u8, nbytes: s32) -> s32 #foreign libpq; 391 | PQendcopy :: (conn: *PGconn) -> s32 #foreign libpq; 392 | 393 | /* Set blocking/nonblocking connection to the backend */ 394 | PQsetnonblocking :: (conn: *PGconn, arg: s32) -> s32 #foreign libpq; 395 | PQisnonblocking :: (conn: *PGconn) -> s32 #foreign libpq; 396 | PQisthreadsafe :: () -> s32 #foreign libpq; 397 | PQping :: (conninfo: *u8) -> PGPing #foreign libpq; 398 | PQpingParams :: (keywords: **u8, values: **u8, expand_dbname: s32) -> PGPing #foreign libpq; 399 | 400 | /* Force the write buffer to be written (or at least try) */ 401 | PQflush :: (conn: *PGconn) -> s32 #foreign libpq; 402 | 403 | /* 404 | * "Fast path" interface --- not really recommended for application 405 | * use 406 | */ 407 | PQfn :: (conn: *PGconn, fnid: s32, result_buf: *s32, result_len: *s32, result_is_int: s32, args: *PQArgBlock, nargs: s32) -> *PGresult #foreign libpq; 408 | 409 | /* Accessor functions for PGresult objects */ 410 | PQresultStatus :: (res: *PGresult) -> ExecStatusType #foreign libpq; 411 | PQresStatus :: (status: ExecStatusType) -> *u8 #foreign libpq; 412 | PQresultErrorMessage :: (res: *PGresult) -> *u8 #foreign libpq; 413 | PQresultVerboseErrorMessage :: (res: *PGresult, verbosity: PGVerbosity, show_context: PGContextVisibility) -> *u8 #foreign libpq; 414 | 415 | PQresultErrorField :: (res: *PGresult, fieldcode: s32) -> *u8 #foreign libpq; 416 | PQntuples :: (res: *PGresult) -> s32 #foreign libpq; 417 | PQnfields :: (res: *PGresult) -> s32 #foreign libpq; 418 | PQbinaryTuples :: (res: *PGresult) -> s32 #foreign libpq; 419 | PQfname :: (res: *PGresult, field_num: s32) -> *u8 #foreign libpq; 420 | PQfnumber :: (res: *PGresult, field_name: *u8) -> s32 #foreign libpq; 421 | PQftable :: (res: *PGresult, field_num: s32) -> Oid #foreign libpq; 422 | PQftablecol :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 423 | PQfformat :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 424 | PQftype :: (res: *PGresult, field_num: s32) -> Oid #foreign libpq; 425 | PQfsize :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 426 | PQfmod :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 427 | PQcmdStatus :: (res: *PGresult) -> *u8 #foreign libpq; 428 | PQoidStatus :: (res: *PGresult) -> *u8 #foreign libpq; 429 | PQoidValue :: (res: *PGresult) -> Oid #foreign libpq; 430 | PQcmdTuples :: (res: *PGresult) -> *u8 #foreign libpq; 431 | PQgetvalue :: (res: *PGresult, tup_num: s32, field_num: s32) -> *u8 #foreign libpq; 432 | PQgetlength :: (res: *PGresult, tup_num: s32, field_num: s32) -> s32 #foreign libpq; 433 | PQgetisnull :: (res: *PGresult, tup_num: s32, field_num: s32) -> s32 #foreign libpq; 434 | PQnparams :: (res: *PGresult) -> s32 #foreign libpq; 435 | PQparamtype :: (res: *PGresult, param_num: s32) -> Oid #foreign libpq; 436 | 437 | /* Describe prepared statements and portals */ 438 | PQdescribePrepared :: (conn: *PGconn, stmt: *u8) -> *PGresult #foreign libpq; 439 | PQdescribePortal :: (conn: *PGconn, portal: *u8) -> *PGresult #foreign libpq; 440 | PQsendDescribePrepared :: (conn: *PGconn, stmt: *u8) -> s32 #foreign libpq; 441 | PQsendDescribePortal :: (conn: *PGconn, portal: *u8) -> s32 #foreign libpq; 442 | 443 | /* Delete a PGresult */ 444 | PQclear :: (res: *PGresult) -> void #foreign libpq; 445 | 446 | /* For freeing other alloc'd results, such as PGnotify structs */ 447 | PQfreemem :: (ptr: *void) -> void #foreign libpq; 448 | 449 | /* Create and manipulate PGresults */ 450 | PQmakeEmptyPGresult :: (conn: *PGconn, status: ExecStatusType) -> *PGresult #foreign libpq; 451 | PQcopyResult :: (src: *PGresult, flags: s32) -> *PGresult #foreign libpq; 452 | PQsetResultAttrs :: (res: *PGresult, numAttributes: s32, attDescs: *PGresAttDesc) -> s32 #foreign libpq; 453 | PQresultAlloc :: (res: *PGresult, nBytes: size_t) -> *void #foreign libpq; 454 | PQsetvalue :: (res: *PGresult, tup_num: s32, field_num: s32, value: *u8, len: s32) -> s32 #foreign libpq; 455 | 456 | /* Quoting strings before inclusion in queries. */ 457 | PQescapeStringConn :: (conn: *PGconn, to: *u8, from: *u8, length: size_t, error: *s32) -> size_t #foreign libpq; 458 | 459 | PQescapeLiteral :: (conn: *PGconn, str: *u8, len: size_t) -> *u8 #foreign libpq; 460 | PQescapeIdentifier :: (conn: *PGconn, str: *u8, len: size_t) -> *u8 #foreign libpq; 461 | PQescapeByteaConn :: (conn: *PGconn, from: *u8, from_length: size_t, to_length: *size_t) -> *u8 #foreign libpq; 462 | 463 | PQunescapeBytea :: (strtext: *u8, retbuflen: *size_t) -> *u8 #foreign libpq; 464 | 465 | /* These forms are deprecated! */ 466 | PQescapeString :: (to: *u8, from: *u8, length: size_t) -> size_t #foreign libpq; 467 | PQescapeBytea :: (from: *u8, from_length: size_t, to_length: *size_t) -> *u8 #foreign libpq; 468 | 469 | /* === in fe-print.c === */ 470 | PQprint :: (fout: *FILE, res: *PGresult, ps: *PQprintOpt) -> void #foreign libpq; 471 | 472 | /* 473 | * really old printing routines 474 | */ 475 | PQdisplayTuples :: (res: *PGresult, fp: *FILE, fillAlign: s32, fieldSep: *u8, printHeader: s32, quiet: s32) -> void #foreign libpq; 476 | 477 | PQprintTuples :: (res: *PGresult, fout: *FILE, printAttName: s32, terseOutput: s32, width: s32) -> void #foreign libpq; 478 | 479 | /* Large-object access routines */ 480 | lo_open :: (conn: *PGconn, lobjId: Oid, mode: s32) -> s32 #foreign libpq; 481 | lo_close :: (conn: *PGconn, fd: s32) -> s32 #foreign libpq; 482 | lo_read :: (conn: *PGconn, fd: s32, buf: *u8, len: size_t) -> s32 #foreign libpq; 483 | lo_write :: (conn: *PGconn, fd: s32, buf: *u8, len: size_t) -> s32 #foreign libpq; 484 | lo_lseek :: (conn: *PGconn, fd: s32, offset: s32, whence: s32) -> s32 #foreign libpq; 485 | lo_lseek64 :: (conn: *PGconn, fd: s32, offset: pg_int64, whence: s32) -> pg_int64 #foreign libpq; 486 | lo_creat :: (conn: *PGconn, mode: s32) -> Oid #foreign libpq; 487 | lo_create :: (conn: *PGconn, lobjId: Oid) -> Oid #foreign libpq; 488 | lo_tell :: (conn: *PGconn, fd: s32) -> s32 #foreign libpq; 489 | lo_tell64 :: (conn: *PGconn, fd: s32) -> pg_int64 #foreign libpq; 490 | lo_truncate :: (conn: *PGconn, fd: s32, len: size_t) -> s32 #foreign libpq; 491 | lo_truncate64 :: (conn: *PGconn, fd: s32, len: pg_int64) -> s32 #foreign libpq; 492 | lo_unlink :: (conn: *PGconn, lobjId: Oid) -> s32 #foreign libpq; 493 | lo_import :: (conn: *PGconn, filename: *u8) -> Oid #foreign libpq; 494 | lo_import_with_oid :: (conn: *PGconn, filename: *u8, lobjId: Oid) -> Oid #foreign libpq; 495 | lo_export :: (conn: *PGconn, lobjId: Oid, filename: *u8) -> s32 #foreign libpq; 496 | 497 | /* Get the version of the libpq library in use */ 498 | PQlibVersion :: () -> s32 #foreign libpq; 499 | 500 | /* Determine length of multibyte encoded char at *s */ 501 | PQmblen :: (s: *u8, encoding: s32) -> s32 #foreign libpq; 502 | 503 | /* Determine display length of multibyte encoded char at *s */ 504 | PQdsplen :: (s: *u8, encoding: s32) -> s32 #foreign libpq; 505 | 506 | /* Get encoding id from environment variable PGCLIENTENCODING */ 507 | PQenv2encoding :: () -> s32 #foreign libpq; 508 | 509 | /* === in fe-auth.c === */ 510 | PQencryptPassword :: (passwd: *u8, user: *u8) -> *u8 #foreign libpq; 511 | PQencryptPasswordConn :: (conn: *PGconn, passwd: *u8, user: *u8, algorithm: *u8) -> *u8 #foreign libpq; 512 | 513 | /* === in encnames.c === */ 514 | pg_char_to_encoding :: (name: *u8) -> s32 #foreign libpq; 515 | pg_encoding_to_char :: (encoding: s32) -> *u8 #foreign libpq; 516 | pg_valid_server_encoding_id :: (encoding: s32) -> s32 #foreign libpq; 517 | 518 | #scope_file 519 | 520 | 521 | libpq :: #system_library "libpq"; 522 | 523 | -------------------------------------------------------------------------------- /bindings/windows.jai: -------------------------------------------------------------------------------- 1 | // 2 | // This file was auto-generated using the following command: 3 | // 4 | // jai generate.jai 5 | // 6 | 7 | 8 | 9 | InvalidOid :: cast(Oid) 0; 10 | 11 | LIBPQ_HAS_PIPELINING :: 1; 12 | 13 | LIBPQ_HAS_TRACE_FLAGS :: 1; 14 | 15 | LIBPQ_HAS_SSL_LIBRARY_DETECTION :: 1; 16 | 17 | PG_COPYRES_ATTRS :: 0x01; 18 | PG_COPYRES_TUPLES :: 0x02; 19 | PG_COPYRES_EVENTS :: 0x04; 20 | PG_COPYRES_NOTICEHOOKS :: 0x08; 21 | 22 | PQTRACE_SUPPRESS_TIMESTAMPS :: 1<<0; 23 | 24 | PQTRACE_REGRESS_MODE :: 1<<1; 25 | 26 | PQ_QUERY_PARAM_MAX_LIMIT :: 65535; 27 | 28 | PQnoPasswordSupplied :: "fe_sendauth: no password supplied\n"; 29 | 30 | /* 31 | * Object ID is a fundamental type in Postgres. 32 | */ 33 | Oid :: u32; 34 | 35 | /* Define a signed 64-bit integer type for use in client API declarations. */ 36 | pg_int64 :: s64; 37 | 38 | /* 39 | * Although it is okay to add to these lists, values which become unused 40 | * should never be removed, nor should constants be redefined - that would 41 | * break compatibility with existing code. 42 | */ 43 | ConnStatusType :: enum s32 { 44 | CONNECTION_OK :: 0; 45 | CONNECTION_BAD :: 1; 46 | 47 | CONNECTION_STARTED :: 2; 48 | CONNECTION_MADE :: 3; 49 | CONNECTION_AWAITING_RESPONSE :: 4; 50 | 51 | CONNECTION_AUTH_OK :: 5; 52 | 53 | CONNECTION_SETENV :: 6; 54 | CONNECTION_SSL_STARTUP :: 7; 55 | CONNECTION_NEEDED :: 8; 56 | CONNECTION_CHECK_WRITABLE :: 9; 57 | CONNECTION_CONSUME :: 10; 58 | CONNECTION_GSS_STARTUP :: 11; 59 | CONNECTION_CHECK_TARGET :: 12; 60 | CONNECTION_CHECK_STANDBY :: 13; 61 | } 62 | 63 | PostgresPollingStatusType :: enum s32 { 64 | PGRES_POLLING_FAILED :: 0; 65 | PGRES_POLLING_READING :: 1; 66 | PGRES_POLLING_WRITING :: 2; 67 | PGRES_POLLING_OK :: 3; 68 | PGRES_POLLING_ACTIVE :: 4; 69 | } 70 | 71 | ExecStatusType :: enum s32 { 72 | PGRES_EMPTY_QUERY :: 0; 73 | PGRES_COMMAND_OK :: 1; 74 | 75 | PGRES_TUPLES_OK :: 2; 76 | 77 | PGRES_COPY_OUT :: 3; 78 | PGRES_COPY_IN :: 4; 79 | PGRES_BAD_RESPONSE :: 5; 80 | 81 | PGRES_NONFATAL_ERROR :: 6; 82 | PGRES_FATAL_ERROR :: 7; 83 | PGRES_COPY_BOTH :: 8; 84 | PGRES_SINGLE_TUPLE :: 9; 85 | PGRES_PIPELINE_SYNC :: 10; 86 | PGRES_PIPELINE_ABORTED :: 11; 87 | } 88 | 89 | PGTransactionStatusType :: enum s32 { 90 | PQTRANS_IDLE :: 0; 91 | PQTRANS_ACTIVE :: 1; 92 | PQTRANS_INTRANS :: 2; 93 | PQTRANS_INERROR :: 3; 94 | PQTRANS_UNKNOWN :: 4; 95 | } 96 | 97 | PGVerbosity :: enum s32 { 98 | PQERRORS_TERSE :: 0; 99 | PQERRORS_DEFAULT :: 1; 100 | PQERRORS_VERBOSE :: 2; 101 | PQERRORS_SQLSTATE :: 3; 102 | } 103 | 104 | PGContextVisibility :: enum s32 { 105 | PQSHOW_CONTEXT_NEVER :: 0; 106 | PQSHOW_CONTEXT_ERRORS :: 1; 107 | PQSHOW_CONTEXT_ALWAYS :: 2; 108 | } 109 | 110 | /* 111 | * PGPing - The ordering of this enum should not be altered because the 112 | * values are exposed externally via pg_isready. 113 | */ 114 | PGPing :: enum s32 { 115 | PQPING_OK :: 0; 116 | PQPING_REJECT :: 1; 117 | PQPING_NO_RESPONSE :: 2; 118 | PQPING_NO_ATTEMPT :: 3; 119 | } 120 | 121 | /* 122 | * PGpipelineStatus - Current status of pipeline mode 123 | */ 124 | PGpipelineStatus :: enum s32 { 125 | PQ_PIPELINE_OFF :: 0; 126 | PQ_PIPELINE_ON :: 1; 127 | PQ_PIPELINE_ABORTED :: 2; 128 | } 129 | 130 | pg_conn :: struct {} 131 | /* PGconn encapsulates a connection to the backend. 132 | * The contents of this struct are not supposed to be known to applications. 133 | */ 134 | PGconn :: pg_conn; 135 | 136 | pg_result :: struct {} 137 | /* PGresult encapsulates the result of a query (or more precisely, of a single 138 | * SQL command --- a query string given to PQsendQuery can contain multiple 139 | * commands and thus return multiple PGresult objects). 140 | * The contents of this struct are not supposed to be known to applications. 141 | */ 142 | PGresult :: pg_result; 143 | 144 | pg_cancel :: struct {} 145 | /* PGcancel encapsulates the information needed to cancel a running 146 | * query on an existing connection. 147 | * The contents of this struct are not supposed to be known to applications. 148 | */ 149 | PGcancel :: pg_cancel; 150 | 151 | /* PGnotify represents the occurrence of a NOTIFY message. 152 | * Ideally this would be an opaque typedef, but it's so simple that it's 153 | * unlikely to change. 154 | * NOTE: in Postgres 6.4 and later, the be_pid is the notifying backend's, 155 | * whereas in earlier versions it was always your own backend's PID. 156 | */ 157 | pgNotify :: struct { 158 | relname: *u8; /* notification condition name */ 159 | be_pid: s32; /* process ID of notifying server process */ 160 | extra: *u8; /* notification parameter */ 161 | 162 | next: *pgNotify; /* list link */ 163 | } 164 | 165 | /* PGnotify represents the occurrence of a NOTIFY message. 166 | * Ideally this would be an opaque typedef, but it's so simple that it's 167 | * unlikely to change. 168 | * NOTE: in Postgres 6.4 and later, the be_pid is the notifying backend's, 169 | * whereas in earlier versions it was always your own backend's PID. 170 | */ 171 | PGnotify :: pgNotify; 172 | 173 | /* Function types for notice-handling callbacks */ 174 | PQnoticeReceiver :: #type (arg: *void, res: *PGresult) -> void #c_call; 175 | PQnoticeProcessor :: #type (arg: *void, message: *u8) -> void #c_call; 176 | 177 | /* Print options for PQprint() */ 178 | pqbool :: u8; 179 | 180 | _PQprintOpt :: struct { 181 | header: pqbool; /* print output field headings and row count */ 182 | align: pqbool; /* fill align the fields */ 183 | standard: pqbool; /* old brain dead format */ 184 | html3: pqbool; /* output html tables */ 185 | expanded: pqbool; /* expand tables */ 186 | pager: pqbool; /* use pager for output if needed */ 187 | fieldSep: *u8; /* field separator */ 188 | tableOpt: *u8; /* insert to HTML */ 189 | caption: *u8; /* HTML
*/ 190 | /* null terminated array of replacement field 191 | * names */ 192 | fieldName: **u8; 193 | } 194 | 195 | PQprintOpt :: _PQprintOpt; 196 | 197 | /* ---------------- 198 | * Structure for the conninfo parameter definitions returned by PQconndefaults 199 | * or PQconninfoParse. 200 | * 201 | * All fields except "val" point at static strings which must not be altered. 202 | * "val" is either NULL or a malloc'd current-value string. PQconninfoFree() 203 | * will release both the val strings and the PQconninfoOption array itself. 204 | * ---------------- 205 | */ 206 | _PQconninfoOption :: struct { 207 | keyword: *u8; /* The keyword of the option */ 208 | envvar: *u8; /* Fallback environment variable name */ 209 | compiled: *u8; /* Fallback compiled in default value */ 210 | val: *u8; /* Option's current value, or NULL */ 211 | label: *u8; /* Label for field in connect dialog */ 212 | /* Indicates how to display this field in a 213 | * connect dialog. Values are: "" Display 214 | * entered value as is "*" Password field - 215 | * hide value "D" Debug option - don't show 216 | * by default */ 217 | dispchar: *u8; 218 | 219 | dispsize: s32; /* Field size in characters for dialog */ 220 | } 221 | 222 | /* ---------------- 223 | * Structure for the conninfo parameter definitions returned by PQconndefaults 224 | * or PQconninfoParse. 225 | * 226 | * All fields except "val" point at static strings which must not be altered. 227 | * "val" is either NULL or a malloc'd current-value string. PQconninfoFree() 228 | * will release both the val strings and the PQconninfoOption array itself. 229 | * ---------------- 230 | */ 231 | PQconninfoOption :: _PQconninfoOption; 232 | 233 | /* ---------------- 234 | * PQArgBlock -- structure for PQfn() arguments 235 | * ---------------- 236 | */ 237 | PQArgBlock :: struct { 238 | len: s32; 239 | isint: s32; 240 | u: union { 241 | ptr: *s32; /* can't use void (dec compiler barfs) */ 242 | integer: s32; 243 | }; 244 | } 245 | 246 | /* ---------------- 247 | * PGresAttDesc -- Data about a single attribute (column) of a query result 248 | * ---------------- 249 | */ 250 | pgresAttDesc :: struct { 251 | name: *u8; /* column name */ 252 | tableid: Oid; /* source table, if known */ 253 | columnid: s32; /* source column, if known */ 254 | format: s32; /* format code for value (text/binary) */ 255 | typid: Oid; /* type id */ 256 | typlen: s32; /* type size */ 257 | atttypmod: s32; /* type-specific modifier info */ 258 | } 259 | 260 | /* ---------------- 261 | * PGresAttDesc -- Data about a single attribute (column) of a query result 262 | * ---------------- 263 | */ 264 | PGresAttDesc :: pgresAttDesc; 265 | 266 | /* make a new client connection to the backend */ 267 | /* Asynchronous (non-blocking) */ 268 | PQconnectStart :: (conninfo: *u8) -> *PGconn #foreign libpq; 269 | PQconnectStartParams :: (keywords: **u8, values: **u8, expand_dbname: s32) -> *PGconn #foreign libpq; 270 | 271 | PQconnectPoll :: (conn: *PGconn) -> PostgresPollingStatusType #foreign libpq; 272 | 273 | /* Synchronous (blocking) */ 274 | PQconnectdb :: (conninfo: *u8) -> *PGconn #foreign libpq; 275 | PQconnectdbParams :: (keywords: **u8, values: **u8, expand_dbname: s32) -> *PGconn #foreign libpq; 276 | 277 | PQsetdbLogin :: (pghost: *u8, pgport: *u8, pgoptions: *u8, pgtty: *u8, dbName: *u8, login: *u8, pwd: *u8) -> *PGconn #foreign libpq; 278 | 279 | /* close the current connection and free the PGconn data structure */ 280 | PQfinish :: (conn: *PGconn) -> void #foreign libpq; 281 | 282 | /* get info about connection options known to PQconnectdb */ 283 | PQconndefaults :: () -> *PQconninfoOption #foreign libpq; 284 | 285 | /* parse connection options in same way as PQconnectdb */ 286 | PQconninfoParse :: (conninfo: *u8, errmsg: **u8) -> *PQconninfoOption #foreign libpq; 287 | 288 | /* return the connection options used by a live connection */ 289 | PQconninfo :: (conn: *PGconn) -> *PQconninfoOption #foreign libpq; 290 | 291 | /* free the data structure returned by PQconndefaults() or PQconninfoParse() */ 292 | PQconninfoFree :: (connOptions: *PQconninfoOption) -> void #foreign libpq; 293 | 294 | /* 295 | * close the current connection and reestablish a new one with the same 296 | * parameters 297 | */ 298 | /* Asynchronous (non-blocking) */ 299 | PQresetStart :: (conn: *PGconn) -> s32 #foreign libpq; 300 | PQresetPoll :: (conn: *PGconn) -> PostgresPollingStatusType #foreign libpq; 301 | 302 | /* Synchronous (blocking) */ 303 | PQreset :: (conn: *PGconn) -> void #foreign libpq; 304 | 305 | /* request a cancel structure */ 306 | PQgetCancel :: (conn: *PGconn) -> *PGcancel #foreign libpq; 307 | 308 | /* free a cancel structure */ 309 | PQfreeCancel :: (cancel: *PGcancel) -> void #foreign libpq; 310 | 311 | /* issue a cancel request */ 312 | PQcancel :: (cancel: *PGcancel, errbuf: *u8, errbufsize: s32) -> s32 #foreign libpq; 313 | 314 | /* backwards compatible version of PQcancel; not thread-safe */ 315 | PQrequestCancel :: (conn: *PGconn) -> s32 #foreign libpq; 316 | 317 | /* Accessor functions for PGconn objects */ 318 | PQdb :: (conn: *PGconn) -> *u8 #foreign libpq; 319 | PQuser :: (conn: *PGconn) -> *u8 #foreign libpq; 320 | PQpass :: (conn: *PGconn) -> *u8 #foreign libpq; 321 | PQhost :: (conn: *PGconn) -> *u8 #foreign libpq; 322 | PQhostaddr :: (conn: *PGconn) -> *u8 #foreign libpq; 323 | PQport :: (conn: *PGconn) -> *u8 #foreign libpq; 324 | PQtty :: (conn: *PGconn) -> *u8 #foreign libpq; 325 | PQoptions :: (conn: *PGconn) -> *u8 #foreign libpq; 326 | PQstatus :: (conn: *PGconn) -> ConnStatusType #foreign libpq; 327 | PQtransactionStatus :: (conn: *PGconn) -> PGTransactionStatusType #foreign libpq; 328 | PQparameterStatus :: (conn: *PGconn, paramName: *u8) -> *u8 #foreign libpq; 329 | 330 | PQprotocolVersion :: (conn: *PGconn) -> s32 #foreign libpq; 331 | PQserverVersion :: (conn: *PGconn) -> s32 #foreign libpq; 332 | PQerrorMessage :: (conn: *PGconn) -> *u8 #foreign libpq; 333 | PQsocket :: (conn: *PGconn) -> s32 #foreign libpq; 334 | PQbackendPID :: (conn: *PGconn) -> s32 #foreign libpq; 335 | PQpipelineStatus :: (conn: *PGconn) -> PGpipelineStatus #foreign libpq; 336 | PQconnectionNeedsPassword :: (conn: *PGconn) -> s32 #foreign libpq; 337 | PQconnectionUsedPassword :: (conn: *PGconn) -> s32 #foreign libpq; 338 | PQclientEncoding :: (conn: *PGconn) -> s32 #foreign libpq; 339 | PQsetClientEncoding :: (conn: *PGconn, encoding: *u8) -> s32 #foreign libpq; 340 | 341 | /* SSL information functions */ 342 | PQsslInUse :: (conn: *PGconn) -> s32 #foreign libpq; 343 | PQsslStruct :: (conn: *PGconn, struct_name: *u8) -> *void #foreign libpq; 344 | PQsslAttribute :: (conn: *PGconn, attribute_name: *u8) -> *u8 #foreign libpq; 345 | PQsslAttributeNames :: (conn: *PGconn) -> **u8 #foreign libpq; 346 | 347 | /* Get the OpenSSL structure associated with a connection. Returns NULL for 348 | * unencrypted connections or if any other TLS library is in use. */ 349 | PQgetssl :: (conn: *PGconn) -> *void #foreign libpq; 350 | 351 | /* Tell libpq whether it needs to initialize OpenSSL */ 352 | PQinitSSL :: (do_init: s32) -> void #foreign libpq; 353 | 354 | /* More detailed way to tell libpq whether it needs to initialize OpenSSL */ 355 | PQinitOpenSSL :: (do_ssl: s32, do_crypto: s32) -> void #foreign libpq; 356 | 357 | /* Return true if GSSAPI encryption is in use */ 358 | PQgssEncInUse :: (conn: *PGconn) -> s32 #foreign libpq; 359 | 360 | /* Returns GSSAPI context if GSSAPI is in use */ 361 | PQgetgssctx :: (conn: *PGconn) -> *void #foreign libpq; 362 | 363 | /* Set verbosity for PQerrorMessage and PQresultErrorMessage */ 364 | PQsetErrorVerbosity :: (conn: *PGconn, verbosity: PGVerbosity) -> PGVerbosity #foreign libpq; 365 | 366 | /* Set CONTEXT visibility for PQerrorMessage and PQresultErrorMessage */ 367 | PQsetErrorContextVisibility :: (conn: *PGconn, show_context: PGContextVisibility) -> PGContextVisibility #foreign libpq; 368 | 369 | /* Override default notice handling routines */ 370 | PQsetNoticeReceiver :: (conn: *PGconn, proc: PQnoticeReceiver, arg: *void) -> PQnoticeReceiver #foreign libpq; 371 | 372 | PQsetNoticeProcessor :: (conn: *PGconn, proc: PQnoticeProcessor, arg: *void) -> PQnoticeProcessor #foreign libpq; 373 | 374 | /* 375 | * Used to set callback that prevents concurrent access to 376 | * non-thread safe functions that libpq needs. 377 | * The default implementation uses a libpq internal mutex. 378 | * Only required for multithreaded apps that use kerberos 379 | * both within their app and for postgresql connections. 380 | */ 381 | pgthreadlock_t :: #type (acquire: s32) -> void #c_call; 382 | 383 | PQregisterThreadLock :: (newhandler: pgthreadlock_t) -> pgthreadlock_t #foreign libpq; 384 | 385 | /* === in fe-trace.c === */ 386 | PQtrace :: (conn: *PGconn, debug_port: *FILE) -> void #foreign libpq; 387 | PQuntrace :: (conn: *PGconn) -> void #foreign libpq; 388 | 389 | PQsetTraceFlags :: (conn: *PGconn, flags: s32) -> void #foreign libpq; 390 | 391 | /* Simple synchronous query */ 392 | PQexec :: (conn: *PGconn, query: *u8) -> *PGresult #foreign libpq; 393 | PQexecParams :: (conn: *PGconn, command: *u8, nParams: s32, paramTypes: *Oid, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> *PGresult #foreign libpq; 394 | 395 | PQprepare :: (conn: *PGconn, stmtName: *u8, query: *u8, nParams: s32, paramTypes: *Oid) -> *PGresult #foreign libpq; 396 | 397 | PQexecPrepared :: (conn: *PGconn, stmtName: *u8, nParams: s32, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> *PGresult #foreign libpq; 398 | 399 | PQsendQuery :: (conn: *PGconn, query: *u8) -> s32 #foreign libpq; 400 | PQsendQueryParams :: (conn: *PGconn, command: *u8, nParams: s32, paramTypes: *Oid, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> s32 #foreign libpq; 401 | 402 | PQsendPrepare :: (conn: *PGconn, stmtName: *u8, query: *u8, nParams: s32, paramTypes: *Oid) -> s32 #foreign libpq; 403 | 404 | PQsendQueryPrepared :: (conn: *PGconn, stmtName: *u8, nParams: s32, paramValues: **u8, paramLengths: *s32, paramFormats: *s32, resultFormat: s32) -> s32 #foreign libpq; 405 | 406 | PQsetSingleRowMode :: (conn: *PGconn) -> s32 #foreign libpq; 407 | PQgetResult :: (conn: *PGconn) -> *PGresult #foreign libpq; 408 | 409 | /* Routines for managing an asynchronous query */ 410 | PQisBusy :: (conn: *PGconn) -> s32 #foreign libpq; 411 | PQconsumeInput :: (conn: *PGconn) -> s32 #foreign libpq; 412 | 413 | /* Routines for pipeline mode management */ 414 | PQenterPipelineMode :: (conn: *PGconn) -> s32 #foreign libpq; 415 | PQexitPipelineMode :: (conn: *PGconn) -> s32 #foreign libpq; 416 | PQpipelineSync :: (conn: *PGconn) -> s32 #foreign libpq; 417 | PQsendFlushRequest :: (conn: *PGconn) -> s32 #foreign libpq; 418 | 419 | /* LISTEN/NOTIFY support */ 420 | PQnotifies :: (conn: *PGconn) -> *PGnotify #foreign libpq; 421 | 422 | /* Routines for copy in/out */ 423 | PQputCopyData :: (conn: *PGconn, buffer: *u8, nbytes: s32) -> s32 #foreign libpq; 424 | PQputCopyEnd :: (conn: *PGconn, errormsg: *u8) -> s32 #foreign libpq; 425 | PQgetCopyData :: (conn: *PGconn, buffer: **u8, async: s32) -> s32 #foreign libpq; 426 | 427 | /* Deprecated routines for copy in/out */ 428 | PQgetline :: (conn: *PGconn, _string: *u8, length: s32) -> s32 #foreign libpq; 429 | PQputline :: (conn: *PGconn, _string: *u8) -> s32 #foreign libpq; 430 | PQgetlineAsync :: (conn: *PGconn, buffer: *u8, bufsize: s32) -> s32 #foreign libpq; 431 | PQputnbytes :: (conn: *PGconn, buffer: *u8, nbytes: s32) -> s32 #foreign libpq; 432 | PQendcopy :: (conn: *PGconn) -> s32 #foreign libpq; 433 | 434 | /* Set blocking/nonblocking connection to the backend */ 435 | PQsetnonblocking :: (conn: *PGconn, arg: s32) -> s32 #foreign libpq; 436 | PQisnonblocking :: (conn: *PGconn) -> s32 #foreign libpq; 437 | PQisthreadsafe :: () -> s32 #foreign libpq; 438 | PQping :: (conninfo: *u8) -> PGPing #foreign libpq; 439 | PQpingParams :: (keywords: **u8, values: **u8, expand_dbname: s32) -> PGPing #foreign libpq; 440 | 441 | /* Force the write buffer to be written (or at least try) */ 442 | PQflush :: (conn: *PGconn) -> s32 #foreign libpq; 443 | 444 | /* 445 | * "Fast path" interface --- not really recommended for application 446 | * use 447 | */ 448 | PQfn :: (conn: *PGconn, fnid: s32, result_buf: *s32, result_len: *s32, result_is_int: s32, args: *PQArgBlock, nargs: s32) -> *PGresult #foreign libpq; 449 | 450 | /* Accessor functions for PGresult objects */ 451 | PQresultStatus :: (res: *PGresult) -> ExecStatusType #foreign libpq; 452 | PQresStatus :: (status: ExecStatusType) -> *u8 #foreign libpq; 453 | PQresultErrorMessage :: (res: *PGresult) -> *u8 #foreign libpq; 454 | PQresultVerboseErrorMessage :: (res: *PGresult, verbosity: PGVerbosity, show_context: PGContextVisibility) -> *u8 #foreign libpq; 455 | 456 | PQresultErrorField :: (res: *PGresult, fieldcode: s32) -> *u8 #foreign libpq; 457 | PQntuples :: (res: *PGresult) -> s32 #foreign libpq; 458 | PQnfields :: (res: *PGresult) -> s32 #foreign libpq; 459 | PQbinaryTuples :: (res: *PGresult) -> s32 #foreign libpq; 460 | PQfname :: (res: *PGresult, field_num: s32) -> *u8 #foreign libpq; 461 | PQfnumber :: (res: *PGresult, field_name: *u8) -> s32 #foreign libpq; 462 | PQftable :: (res: *PGresult, field_num: s32) -> Oid #foreign libpq; 463 | PQftablecol :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 464 | PQfformat :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 465 | PQftype :: (res: *PGresult, field_num: s32) -> Oid #foreign libpq; 466 | PQfsize :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 467 | PQfmod :: (res: *PGresult, field_num: s32) -> s32 #foreign libpq; 468 | PQcmdStatus :: (res: *PGresult) -> *u8 #foreign libpq; 469 | PQoidStatus :: (res: *PGresult) -> *u8 #foreign libpq; 470 | PQoidValue :: (res: *PGresult) -> Oid #foreign libpq; 471 | PQcmdTuples :: (res: *PGresult) -> *u8 #foreign libpq; 472 | PQgetvalue :: (res: *PGresult, tup_num: s32, field_num: s32) -> *u8 #foreign libpq; 473 | PQgetlength :: (res: *PGresult, tup_num: s32, field_num: s32) -> s32 #foreign libpq; 474 | PQgetisnull :: (res: *PGresult, tup_num: s32, field_num: s32) -> s32 #foreign libpq; 475 | PQnparams :: (res: *PGresult) -> s32 #foreign libpq; 476 | PQparamtype :: (res: *PGresult, param_num: s32) -> Oid #foreign libpq; 477 | 478 | /* Describe prepared statements and portals */ 479 | PQdescribePrepared :: (conn: *PGconn, stmt: *u8) -> *PGresult #foreign libpq; 480 | PQdescribePortal :: (conn: *PGconn, portal: *u8) -> *PGresult #foreign libpq; 481 | PQsendDescribePrepared :: (conn: *PGconn, stmt: *u8) -> s32 #foreign libpq; 482 | PQsendDescribePortal :: (conn: *PGconn, portal: *u8) -> s32 #foreign libpq; 483 | 484 | /* Delete a PGresult */ 485 | PQclear :: (res: *PGresult) -> void #foreign libpq; 486 | 487 | /* For freeing other alloc'd results, such as PGnotify structs */ 488 | PQfreemem :: (ptr: *void) -> void #foreign libpq; 489 | 490 | /* Create and manipulate PGresults */ 491 | PQmakeEmptyPGresult :: (conn: *PGconn, status: ExecStatusType) -> *PGresult #foreign libpq; 492 | PQcopyResult :: (src: *PGresult, flags: s32) -> *PGresult #foreign libpq; 493 | PQsetResultAttrs :: (res: *PGresult, numAttributes: s32, attDescs: *PGresAttDesc) -> s32 #foreign libpq; 494 | PQresultAlloc :: (res: *PGresult, nBytes: size_t) -> *void #foreign libpq; 495 | PQresultMemorySize :: (res: *PGresult) -> size_t #foreign libpq; 496 | PQsetvalue :: (res: *PGresult, tup_num: s32, field_num: s32, value: *u8, len: s32) -> s32 #foreign libpq; 497 | 498 | /* Quoting strings before inclusion in queries. */ 499 | PQescapeStringConn :: (conn: *PGconn, to: *u8, from: *u8, length: size_t, error: *s32) -> size_t #foreign libpq; 500 | 501 | PQescapeLiteral :: (conn: *PGconn, str: *u8, len: size_t) -> *u8 #foreign libpq; 502 | PQescapeIdentifier :: (conn: *PGconn, str: *u8, len: size_t) -> *u8 #foreign libpq; 503 | PQescapeByteaConn :: (conn: *PGconn, from: *u8, from_length: size_t, to_length: *size_t) -> *u8 #foreign libpq; 504 | 505 | PQunescapeBytea :: (strtext: *u8, retbuflen: *size_t) -> *u8 #foreign libpq; 506 | 507 | /* These forms are deprecated! */ 508 | PQescapeString :: (to: *u8, from: *u8, length: size_t) -> size_t #foreign libpq; 509 | PQescapeBytea :: (from: *u8, from_length: size_t, to_length: *size_t) -> *u8 #foreign libpq; 510 | 511 | /* === in fe-print.c === */ 512 | PQprint :: (fout: *FILE, res: *PGresult, ps: *PQprintOpt) -> void #foreign libpq; 513 | 514 | /* 515 | * really old printing routines 516 | */ 517 | PQdisplayTuples :: (res: *PGresult, fp: *FILE, fillAlign: s32, fieldSep: *u8, printHeader: s32, quiet: s32) -> void #foreign libpq; 518 | 519 | PQprintTuples :: (res: *PGresult, fout: *FILE, PrintAttNames: s32, TerseOutput: s32, colWidth: s32) -> void #foreign libpq; 520 | 521 | /* Large-object access routines */ 522 | lo_open :: (conn: *PGconn, lobjId: Oid, mode: s32) -> s32 #foreign libpq; 523 | lo_close :: (conn: *PGconn, fd: s32) -> s32 #foreign libpq; 524 | lo_read :: (conn: *PGconn, fd: s32, buf: *u8, len: size_t) -> s32 #foreign libpq; 525 | lo_write :: (conn: *PGconn, fd: s32, buf: *u8, len: size_t) -> s32 #foreign libpq; 526 | lo_lseek :: (conn: *PGconn, fd: s32, offset: s32, whence: s32) -> s32 #foreign libpq; 527 | lo_lseek64 :: (conn: *PGconn, fd: s32, offset: pg_int64, whence: s32) -> pg_int64 #foreign libpq; 528 | lo_creat :: (conn: *PGconn, mode: s32) -> Oid #foreign libpq; 529 | lo_create :: (conn: *PGconn, lobjId: Oid) -> Oid #foreign libpq; 530 | lo_tell :: (conn: *PGconn, fd: s32) -> s32 #foreign libpq; 531 | lo_tell64 :: (conn: *PGconn, fd: s32) -> pg_int64 #foreign libpq; 532 | lo_truncate :: (conn: *PGconn, fd: s32, len: size_t) -> s32 #foreign libpq; 533 | lo_truncate64 :: (conn: *PGconn, fd: s32, len: pg_int64) -> s32 #foreign libpq; 534 | lo_unlink :: (conn: *PGconn, lobjId: Oid) -> s32 #foreign libpq; 535 | lo_import :: (conn: *PGconn, filename: *u8) -> Oid #foreign libpq; 536 | lo_import_with_oid :: (conn: *PGconn, filename: *u8, lobjId: Oid) -> Oid #foreign libpq; 537 | lo_export :: (conn: *PGconn, lobjId: Oid, filename: *u8) -> s32 #foreign libpq; 538 | 539 | /* Get the version of the libpq library in use */ 540 | PQlibVersion :: () -> s32 #foreign libpq; 541 | 542 | /* Determine length of multibyte encoded char at *s */ 543 | PQmblen :: (s: *u8, encoding: s32) -> s32 #foreign libpq; 544 | 545 | /* Same, but not more than the distance to the end of string s */ 546 | PQmblenBounded :: (s: *u8, encoding: s32) -> s32 #foreign libpq; 547 | 548 | /* Determine display length of multibyte encoded char at *s */ 549 | PQdsplen :: (s: *u8, encoding: s32) -> s32 #foreign libpq; 550 | 551 | /* Get encoding id from environment variable PGCLIENTENCODING */ 552 | PQenv2encoding :: () -> s32 #foreign libpq; 553 | 554 | /* === in fe-auth.c === */ 555 | PQencryptPassword :: (passwd: *u8, user: *u8) -> *u8 #foreign libpq; 556 | PQencryptPasswordConn :: (conn: *PGconn, passwd: *u8, user: *u8, algorithm: *u8) -> *u8 #foreign libpq; 557 | 558 | /* === in encnames.c === */ 559 | pg_char_to_encoding :: (name: *u8) -> s32 #foreign libpq; 560 | pg_encoding_to_char :: (encoding: s32) -> *u8 #foreign libpq; 561 | pg_valid_server_encoding_id :: (encoding: s32) -> s32 #foreign libpq; 562 | 563 | /* Support for overriding sslpassword handling with a callback */ 564 | PQsslKeyPassHook_OpenSSL_type :: #type (buf: *u8, size: s32, conn: *PGconn) -> s32 #c_call; 565 | PQgetSSLKeyPassHook_OpenSSL :: () -> PQsslKeyPassHook_OpenSSL_type #foreign libpq; 566 | PQsetSSLKeyPassHook_OpenSSL :: (hook: PQsslKeyPassHook_OpenSSL_type) -> void #foreign libpq; 567 | PQdefaultSSLKeyPassHook_OpenSSL :: (buf: *u8, size: s32, conn: *PGconn) -> s32 #foreign libpq; 568 | 569 | #scope_file 570 | 571 | 572 | libpq :: #library,no_dll "libpq"; 573 | 574 | -------------------------------------------------------------------------------- /byte_order.jai: -------------------------------------------------------------------------------- 1 | IS_BIG_ENDIAN :: #run big_endian(); 2 | 3 | big_endian :: () -> bool { 4 | x: s16 = 1; 5 | b := cast(*u8) *x; 6 | 7 | if b[0] return false; 8 | return true; 9 | } 10 | 11 | ntoh :: inline (value: $T) -> T { 12 | #if IS_BIG_ENDIAN { 13 | return value; 14 | } else { 15 | return byte_swap(value); 16 | } 17 | } 18 | 19 | hton :: ntoh; 20 | 21 | #scope_file 22 | 23 | #import "Basic"; 24 | #import "Compiler"; 25 | #import "Bit_Operations"; 26 | 27 | -------------------------------------------------------------------------------- /examples/example.jai: -------------------------------------------------------------------------------- 1 | main :: () { 2 | args := get_command_line_arguments(); 3 | if args.count != 2 { 4 | log_error("Usage: % ", args[0]); 5 | exit(1); 6 | } 7 | 8 | db_url := args[1]; 9 | 10 | conn, success := connect(db_url); 11 | defer disconnect(conn); 12 | if !success exit(1); 13 | 14 | // Let’s just test deserializing all the supported column types: 15 | log("Test 1"); 16 | QUERY :: #string END 17 | SELECT 18 | 1::int2, 2::int4, 3::int8, 19 | 2.5::float8, 3.5::float8 as float8_to_4, 4.5::float4, 5.5::float4 as float4_to_8, 6.5::numeric(8, 2), 20 | true "boolean_true", 21 | false "boolean_false", 22 | 'a'::char(3) "char", 'b'::bpchar "bpchar", 'c'::varchar, 'd'::name, 'e'::text, 23 | '2023-05-15'::timestamp, '2023-05-14 UTC'::timestamptz, 24 | '2023-05-15T14:30:00'::timestamp time_wo_tz, '2023-05-15T14:30:00 Europe/Vienna'::timestamptz time_with_tz, '2023-05-15T14:30:00 UTC'::timestamptz time_utc, 25 | '2023-05-15T14:30:00'::timestamp timestamp_apollo, -- Test writing timestamp into Apollo_Time 26 | '2023-10-24'::date real_date, 27 | -- '2023-10-24'::date real_date_apollo, -- Test writing date into Apollo_Time 28 | 'Hello, Sailor!'::bytea sailor_bytes, -- Test [] case 29 | 'Hello, Sailor!'::bytea sailor_bytes_dynamic, -- Test [..] case 30 | 'Hello, Sailor!'::bytea sailor_bytes_fixed, -- Test [n] case 31 | '01951a87-c21c-726c-ba20-e0da37db52cf'::UUID -- UUID (which is a [16]u8) test case 32 | END 33 | 34 | results: [] Test; 35 | results, success = execute(conn, Test, QUERY); 36 | assert(success); 37 | log("Results: %", results); 38 | assert(results.count == 1); 39 | 40 | result := results[0]; 41 | assert(result.int2 == 1); 42 | assert(result.int4 == 2); 43 | assert(result.int8 == 3); 44 | assert(result.float8 == 2.5); 45 | assert(result.float8_to_4 == 3.5); 46 | assert(result.float4 == 4.5); 47 | assert(result.float4_to_8 == 5.5); 48 | assert(result.numeric == "6.50"); 49 | assert(result.boolean_true == true); 50 | assert(result.boolean_false == false); 51 | assert(result.char == "a "); 52 | assert(result.bpchar == "b"); 53 | assert(result.varchar == "c"); 54 | assert(result.name == "d"); 55 | assert(result.text == "e"); 56 | assert(result.timestamp == "2023-05-15T00:00:00.000Z"); 57 | assert(result.timestamptz == 1684022400000000); 58 | assert(calendar_to_iso_string(to_calendar(result.timestamp_apollo)) == "2023-05-15T14:30:00.000Z"); 59 | assert(result.real_date == 8697); // Days since 2000-01-01 60 | // assert(calendar_to_iso_string(to_calendar(result.real_date_apollo)) == "2023-10-24T00:00:00.000Z"); 61 | assert(cast(string) result.sailor_bytes == "Hello, Sailor!"); 62 | assert(cast(string) result.sailor_bytes_dynamic == "Hello, Sailor!"); 63 | assert(cast(string) result.sailor_bytes_fixed == "Hello, Sailor!"); 64 | // These bytes are the UUIDv7: 01951a87-c21c-726c-ba20-e0da37db52cf 65 | assert(uuid_equal(result.uuid, u8.[1, 149, 26, 135, 194, 28, 114, 108, 186, 32, 224, 218, 55, 219, 82, 207])); 66 | log("% vs % vs %", result.time_wo_tz, result.time_with_tz, result.time_utc); 67 | 68 | // Let’s try a serializing-deserializing roundtrip 69 | log("Test 2"); 70 | QUERY2 :: #string END 71 | SELECT 72 | $1::int2, 73 | $2::float8, $3::float4, 74 | $4 "boolean_true", 75 | $5 "boolean_false", 76 | $6::varchar, 77 | $7::text 78 | END 79 | 80 | v1: int = 256; 81 | v2: float64 = 1.5; 82 | v3: float = 2.5; 83 | v4 := true; 84 | v5 := false; 85 | v6: string; 86 | v7 := "Something"; 87 | results, success = execute(conn, Test, QUERY2, v1, v2, v3, v4, v5, v6, v7); 88 | assert(success); 89 | log("Results: %", results); 90 | assert(results.count == 1); 91 | 92 | result = results[0]; 93 | assert(result.int2 == 256); 94 | assert(result.float8 == 1.5); 95 | assert(result.float4 == 2.5); 96 | assert(result.boolean_true == true); 97 | assert(result.boolean_false == false); 98 | assert(result.varchar == ""); 99 | assert(result.text == "Something"); 100 | 101 | log("ALL OK"); 102 | } 103 | 104 | UUID :: [16]u8; 105 | 106 | // UUIDs are just two s64 values, so equality is simply comparing each 107 | // of the two s64 values to each other from u1 and u2 108 | uuid_equal :: (u1: UUID, u2: UUID) -> bool { 109 | a, b := u1.([2]s64,force), u2.([2]s64,force); 110 | return a[0] == b[0] && a[1] == b[1]; 111 | } 112 | 113 | uuid_to_string :: (uuid: UUID) -> string { 114 | 115 | push_context { 116 | 117 | context.print_style.default_format_int.base = 16; 118 | context.print_style.default_format_int.minimum_digits = 2; 119 | 120 | formatted := sprint("%0%0%0%0-%0%0-%0%0-%0%0-%0%0%0%0%0%0", 121 | uuid[0], uuid[1], uuid[2], uuid[3], 122 | uuid[4], uuid[5], 123 | uuid[6], uuid[7], 124 | uuid[8], uuid[9], 125 | uuid[10], uuid[11], 126 | uuid[12], uuid[13], uuid[14], uuid[15], 127 | ); 128 | 129 | return formatted; 130 | } 131 | } 132 | 133 | Test :: struct { 134 | int2: int; 135 | int4: int; 136 | int8: s64; 137 | float8: float64; 138 | float8_to_4: float32; 139 | float4: float32; 140 | float4_to_8: float64; 141 | numeric: string; 142 | boolean_true: bool; 143 | boolean_false: bool; 144 | char: string; 145 | bpchar: string; 146 | varchar: string; 147 | name: string; 148 | text: string; 149 | timestamp: string; 150 | timestamptz: s64; 151 | timestamp_apollo: Apollo_Time; 152 | time_wo_tz: s64; 153 | time_with_tz: s64; 154 | time_utc: s64; 155 | real_date: s64; 156 | // real_date_apollo: Apollo_Time; // Only works with SUPPORT_DATE_AS_STRINGS 157 | sailor_bytes: [] u8; 158 | sailor_bytes_dynamic: [..] u8; 159 | sailor_bytes_fixed: [14] u8; 160 | uuid: UUID; 161 | } 162 | 163 | #import,file "../module.jai"; 164 | #import "Basic"; 165 | -------------------------------------------------------------------------------- /examples/first.jai: -------------------------------------------------------------------------------- 1 | #run { 2 | set_build_options_dc(.{do_output = false}); 3 | 4 | w := compiler_create_workspace(); 5 | options := get_build_options(); 6 | options.output_type = .EXECUTABLE; 7 | options.output_executable_name = "example"; 8 | if OS == .MACOS { 9 | HOMEBREW_LIB_PATH :: "/usr/local/homebrew/lib"; // This is where my x64 libraries live. Adjust to fit your needs. 10 | options.additional_linker_arguments = .["-L", HOMEBREW_LIB_PATH]; 11 | compiler_add_library_search_directory(HOMEBREW_LIB_PATH); 12 | } 13 | 14 | set_build_options(options, w); 15 | 16 | add_build_file("example.jai", w); 17 | } 18 | 19 | #import "Compiler"; 20 | 21 | -------------------------------------------------------------------------------- /first.jai: -------------------------------------------------------------------------------- 1 | #import "Compiler"; 2 | 3 | #run build(); 4 | 5 | // Just try to compile the module source without producing an executable to check for obvious errors. 6 | build :: () { 7 | set_build_options_dc(.{do_output = false}); 8 | 9 | w := compiler_create_workspace(); 10 | build_options := get_build_options(w); 11 | build_options.output_type = .NO_OUTPUT; 12 | set_build_options(build_options, w); 13 | 14 | add_build_file("module.jai", w); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /generate.jai: -------------------------------------------------------------------------------- 1 | AT_COMPILE_TIME :: true; 2 | 3 | WINDOWS_PG_DIR :: "C:/Program Files/PostgreSQL/15"; 4 | IMGUI_PATH :: "src/imgui-1.85"; 5 | 6 | DECLARATIONS_TO_OMIT :: string.[ 7 | ]; 8 | 9 | #if AT_COMPILE_TIME { 10 | #run { 11 | set_build_options_dc(.{do_output=false}); 12 | if !generate_bindings() { 13 | compiler_set_workspace_status(.FAILED); 14 | } 15 | } 16 | } else { 17 | #import "System"; 18 | 19 | main :: () { 20 | set_working_directory(path_strip_filename(get_path_of_running_executable())); 21 | if !generate_bindings() { 22 | exit(1); 23 | } 24 | } 25 | } 26 | 27 | generate_bindings :: () -> bool { 28 | output_filename: string; 29 | opts: Generate_Bindings_Options; 30 | { 31 | using opts; 32 | 33 | make_directory_if_it_does_not_exist("bindings"); 34 | 35 | #if OS == .WINDOWS { 36 | output_filename = "bindings/windows.jai"; 37 | array_add(*include_paths, tprint("%/%", WINDOWS_PG_DIR, "include")); 38 | array_add(*libpaths, tprint("%/%", WINDOWS_PG_DIR, "bin")); 39 | array_add(*libpaths, tprint("%/%", WINDOWS_PG_DIR, "lib")); 40 | generate_library_declarations = false; 41 | footer = FOOTER_WIN; 42 | } else #if OS == .LINUX { 43 | output_filename = "bindings/unix.jai"; 44 | array_add(*include_paths, "/usr/include/postgresql"); 45 | generate_library_declarations = false; 46 | footer = FOOTER_UNIX; 47 | } else #if OS == .MACOS { 48 | // @ToDo: Determine dynamically? Customize? 49 | array_add(*system_include_paths, "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include"); 50 | array_add(*include_paths, "/usr/local/homebrew/opt/libpq/include"); 51 | array_add(*libpaths, "/usr/local/homebrew/opt/libpq/lib"); 52 | output_filename = "bindings/unix.jai"; 53 | generate_library_declarations = false; 54 | footer = FOOTER_UNIX; 55 | } else { 56 | assert(false); 57 | } 58 | 59 | array_add(*libnames, "libpq"); 60 | array_add(*system_include_paths, GENERATOR_DEFAULT_SYSTEM_INCLUDE_PATH); 61 | array_add(*source_files, "libpq-fe.h"); 62 | array_add(*extra_clang_arguments, "-x", "c"); 63 | auto_detect_enum_prefixes = false; 64 | generate_compile_time_struct_checks = false; 65 | strip_flags |= .INLINED_FUNCTIONS; // "inline" functions are not present in the static library. 66 | 67 | // visitor = imgui_visitor; 68 | } 69 | 70 | return generate_bindings(opts, output_filename); 71 | } 72 | 73 | //imgui_visitor :: (decl: *Declaration, parent_decl: *Declaration) -> Declaration_Visit_Result { 74 | // // ImGui has "typedef ints" for each enum. but we want to just use the enums directly, 75 | // // so we get nice type safety, and .unary dots, etc. 76 | // // 77 | // // Look for declarations inside functions (i.e., arguments) or structs (i.e., fields) 78 | // get_associated_enum_name :: (name: string) -> string { 79 | // if name.count > 1 && name[name.count - 1] != #char "_" { 80 | // return tprint("%_", name); 81 | // } 82 | // return name; 83 | // } 84 | 85 | // if !parent_decl { 86 | // if array_find(DECLARATIONS_TO_OMIT, decl.name) { 87 | // decl.decl_flags |= .OMIT_FROM_OUTPUT; 88 | // return .STOP; 89 | // } 90 | // } 91 | 92 | // // Look for function arguments with "typedef" types. 93 | // if parent_decl && 94 | // (parent_decl.kind == .FUNCTION || parent_decl.kind == .STRUCT) && 95 | // decl.kind == .DECLARATION && decl.type.type_of_typedef != null 96 | // { 97 | // old_name := decl.type.type_of_typedef.name; 98 | // if !old_name return .RECURSE; 99 | // new_name := get_associated_enum_name(old_name); 100 | 101 | // //print("% -> %\n", old_name, new_name); 102 | 103 | // for context.generator.global_scope.members { 104 | // if it.kind != .ENUM || it.name != new_name continue; 105 | 106 | // en := cast(*Enum)it; 107 | 108 | // // Don't output the integer typedef 109 | // decl.type.type_of_typedef.decl_flags |= .OMIT_FROM_OUTPUT; 110 | 111 | // // Make sure the enums like "FocusFlags_" get outputted as "FocusFlags" 112 | // if en.output_name.count > 2 && en.output_name[en.output_name.count - 1] == #char "_" { 113 | // en.output_name.count -= 1; 114 | // } 115 | 116 | // // swap in the enum for the typedef 117 | // decl.type.type_of_typedef = null; 118 | // decl.type.type_of_enum = en; 119 | 120 | // // we'll also need to output a cast, like "cast(EnumType)5" for the default argument 121 | // decl.decl_flags |= .NEEDS_DEFAULT_ARGUMENT_CAST; 122 | 123 | // break; 124 | // } 125 | // } 126 | 127 | // if decl.kind == .ENUM { 128 | // // ImGui has lots of enums that should be enum flags. Luckily, they all end in "…Flags_". 129 | // if ends_with(decl.name, "Flags_") { 130 | // en := cast(*Enum)decl; 131 | // en.flags |= .IS_ENUM_FLAGS; 132 | // en.flags |= .VALUES_IN_HEX; 133 | // } 134 | // } 135 | 136 | // if decl.kind == .FUNCTION { 137 | // func := cast(*Function)decl; 138 | // type := func.type.type_of_function; 139 | // // ImGui also has functions with pairs of arguments like "text_begin" and "text_end" 140 | // // for the pointers to the beginning and end of a string. We'll generate wrappers for 141 | // // those function which take a jai string. 142 | // // 143 | // // Here we collect pointers to "_begin" arguments, and use them in 144 | // // get_func_args_for_printing below. 145 | // for type.arguments { 146 | // if it_index == 0 || !ends_with(it.name, "_end") continue; 147 | 148 | // name_part := slice(it.name, 0, it.name.count - "_end".count); 149 | // if !name_part continue; 150 | 151 | // // the first arg may appear as "text_begin" or just "text" 152 | // name_part_with_begin := tprint("%_begin", name_part); 153 | // prev_arg := type.arguments[it_index - 1]; 154 | // if prev_arg.name == name_part || prev_arg.name == name_part_with_begin { 155 | // array_add(*begin_end_string_args, prev_arg); 156 | // func.decl_flags |= .NEEDS_ARGUMENT_WRAPPER; 157 | // } 158 | // } 159 | // } 160 | 161 | // return .RECURSE; 162 | //} 163 | 164 | #import "Basic"; 165 | #import "Bindings_Generator"; 166 | #import "Compiler"; 167 | #import "File"; 168 | #import "String"; 169 | 170 | FOOTER_UNIX :: #string END 171 | 172 | libpq :: #system_library "libpq"; 173 | 174 | END 175 | 176 | FOOTER_WIN :: #string END 177 | 178 | libpq :: #library,no_dll "libpq"; 179 | 180 | END -------------------------------------------------------------------------------- /generate_types.jai: -------------------------------------------------------------------------------- 1 | // Adapted from node-pg-types (https://github.com/brianc/node-pg-types/blob/master/lib/binaryParsers.js) 2 | // From their doc: 3 | // Following query was used to generate this file: 4 | // SELECT json_object_agg(UPPER(PT.typname), PT.oid::int4 ORDER BY pt.oid) 5 | // FROM pg_type PT 6 | // WHERE typnamespace = (SELECT pgn.oid FROM pg_namespace pgn WHERE nspname = 'pg_catalog') -- Take only builting Postgres types with stable OID (extension types are not guaranted to be stable) 7 | // AND typtype = 'b' -- Only basic types 8 | // AND typelem = 0 -- Ignore aliases 9 | // AND typisdefined -- Ignore undefined types 10 | 11 | #run { 12 | #import "Compiler"; 13 | set_build_options_dc(.{do_output = false}); 14 | args := get_build_options().compile_time_command_line; 15 | if args.count != 1 { 16 | log_error("Usage: jai % - ", #file); 17 | exit(1); 18 | } 19 | 20 | db_url := args[0]; 21 | success := generate_types(db_url); 22 | if !success compiler_report("Error while generating types"); 23 | } 24 | 25 | generate_types :: (db_url: string) -> bool { 26 | conn, success := connect(db_url); 27 | defer disconnect(conn); 28 | if !success { 29 | log_error("Could not connect to \"%\"", db_url); 30 | return false; 31 | } 32 | 33 | TYPE_QUERY :: #string END 34 | SELECT UPPER(pt.typname) typname, pt.oid, pt.typarray 35 | FROM pg_type pt 36 | WHERE typnamespace = (SELECT pgn.oid FROM pg_namespace pgn WHERE nspname = 'pg_catalog') -- Take only builting Postgres types with stable OID (extension types are not guaranted to be stable) 37 | AND typtype = 'b' -- Only basic types 38 | AND typname NOT LIKE '\_%' -- ignore names starting with "_", which seem to be 1:1 aliases for the ones without "_" 39 | AND typisdefined -- Ignore undefined types 40 | ORDER BY pt.oid 41 | END 42 | 43 | type_list:, success = execute(conn, Sql_Type, TYPE_QUERY); 44 | if !success { 45 | log_error("Could not fetch types"); 46 | return false; 47 | } 48 | 49 | max_len := 0; 50 | for type_list { 51 | if it.typname.count > max_len max_len = it.typname.count; 52 | } 53 | 54 | builder: String_Builder; 55 | print_to_builder(*builder, "// Generated via \"%\"\n\n", path_filename(#file)); 56 | append(*builder, "Pq_Type :: enum Oid {\n"); 57 | for type_list { 58 | print_to_builder(*builder, " %", it.typname); 59 | for it.typname.count..max_len { 60 | append(*builder, " "); 61 | } 62 | print_to_builder(*builder, ":: %;\n", it.oid); 63 | } 64 | append(*builder, "}\n\n"); 65 | append(*builder, "Pq_Array_Type :: enum Oid {\n"); 66 | for type_list { 67 | if it.typarray == 0 continue; 68 | print_to_builder(*builder, " %", it.typname); 69 | for it.typname.count..max_len { 70 | append(*builder, " "); 71 | } 72 | print_to_builder(*builder, ":: %;\n", it.typarray); 73 | } 74 | append(*builder, "}\n"); 75 | 76 | FILENAME :: "pq_types.jai"; 77 | success = write_entire_file(FILENAME, *builder); 78 | if !success { 79 | log_error("Could not write to file \"%\".", FILENAME); 80 | return false; 81 | } 82 | 83 | return true; 84 | } 85 | 86 | Sql_Type :: struct { 87 | typname: string; 88 | oid: int; 89 | typarray: int; 90 | } 91 | 92 | #import "Basic"; 93 | #import "File"; 94 | #import "String"; 95 | #import,file "module.jai"; 96 | -------------------------------------------------------------------------------- /module.jai: -------------------------------------------------------------------------------- 1 | #module_parameters ()(SUPPORT_DATE_AS_STRINGS := false); // SUPPORT_DATE requires https://github.com/rluba/jai-date, at least for now. 2 | 3 | connect :: (conn_str: string) -> *PGconn, success: bool { 4 | conn_c_str := to_c_string(conn_str); 5 | defer free(conn_c_str); 6 | conn := PQconnectStart(conn_c_str); 7 | if PQstatus(conn) == ConnStatusType.CONNECTION_BAD { 8 | log_error("Couldn’t start connecting\n"); 9 | return conn, false; 10 | } 11 | 12 | result := PQconnectPoll(conn); 13 | while result != PostgresPollingStatusType.PGRES_POLLING_OK && result != PostgresPollingStatusType.PGRES_POLLING_FAILED { 14 | newResult := PQconnectPoll(conn); 15 | // if (result != newResult) { 16 | // print("Connecting… %\n", result); 17 | // } 18 | result = newResult; 19 | } 20 | 21 | if result == PostgresPollingStatusType.PGRES_POLLING_FAILED { 22 | log_error("Couldn’t connect: %\n", PQstatus(conn)); 23 | return conn, false; 24 | } 25 | return conn, true; 26 | } 27 | 28 | disconnect :: (conn: *PGconn) { 29 | if conn { 30 | PQfinish(conn); 31 | } 32 | } 33 | 34 | // Execute a statement and parse the result 35 | execute :: (conn: *PGconn, $T: Type, command: string, args: .. Any, $ignore_unknown := false) -> [] T, success: bool { 36 | success := send_query(conn, command, ..args); 37 | if !success return .[], false; 38 | 39 | query_res := get_last_query_result(conn); 40 | defer PQclear(query_res); 41 | 42 | has_results: bool; 43 | results: [] T; 44 | has_results, success = check_query_result(query_res); 45 | if !success return results, false; 46 | 47 | if has_results { 48 | results, success = get_results(query_res, T, ignore_unknown); 49 | } 50 | return results, success; 51 | } 52 | 53 | // Execute a statement without parsing the result (eg. for DELETE et al) 54 | execute :: (conn: *PGconn, command: string, args: .. Any) -> success: bool, update_rows: int { 55 | success := send_query(conn, command, ..args); 56 | if !success false; 57 | 58 | query_res := get_last_query_result(conn); 59 | defer PQclear(query_res); 60 | 61 | has_results: bool; 62 | update_rows: int; 63 | has_results, success, update_rows = check_query_result(query_res); 64 | 65 | return success, update_rows; 66 | } 67 | 68 | send_query :: (conn: *PGconn, command: string, args: .. Any) -> success: bool { 69 | pool: Pool; 70 | set_allocators(*pool); 71 | defer release(*pool); 72 | 73 | push_allocator(pool_allocator_proc, *pool); 74 | 75 | param_types := NewArray(args.count, Oid, initialized = false); 76 | param_values := NewArray(args.count, *u8, initialized = false); 77 | param_lengths := NewArray(args.count, s32, initialized = false); 78 | param_formats := NewArray(args.count, s32, initialized = false); 79 | 80 | for arg, index: args { 81 | if arg.type.type == { 82 | case .INTEGER; 83 | info := cast(*Type_Info_Integer) arg.type; 84 | // @ToDo: implement unsigned ints 85 | assert(info.signed, "Unsigned not yet supported"); 86 | be_value := cast(*u8) alloc(info.runtime_size); 87 | if info.runtime_size == { 88 | case 2; 89 | param_types[index] = cast(Oid) Pq_Type.INT2; 90 | be_val := hton(<<(cast(*s16) arg.value_pointer)); 91 | memcpy(be_value, *be_val, info.runtime_size); 92 | case 4; 93 | param_types[index] = cast(Oid) Pq_Type.INT4; 94 | be_val := hton(<<(cast(*s32) arg.value_pointer)); 95 | memcpy(be_value, *be_val, info.runtime_size); 96 | case 8; 97 | param_types[index] = cast(Oid) Pq_Type.INT8; 98 | be_val := hton(<<(cast(*s64) arg.value_pointer)); 99 | memcpy(be_value, *be_val, info.runtime_size); 100 | case; 101 | assert(false); 102 | } 103 | param_values[index] = be_value; 104 | param_lengths[index] = cast(s32) info.runtime_size; 105 | param_formats[index] = 1; 106 | 107 | case .FLOAT; 108 | be_value := cast(*u8) alloc(arg.type.runtime_size); 109 | if arg.type.runtime_size == 4 { 110 | param_types[index] = cast(Oid) Pq_Type.FLOAT4; 111 | be_val := hton(<<(cast(*float) arg.value_pointer)); 112 | memcpy(be_value, *be_val, arg.type.runtime_size); 113 | } 114 | if arg.type.runtime_size == 8 { 115 | param_types[index] = cast(Oid) Pq_Type.FLOAT8; 116 | be_val := hton(<<(cast(*float64) arg.value_pointer)); 117 | memcpy(be_value, *be_val, arg.type.runtime_size); 118 | } 119 | param_values[index] = be_value; 120 | param_lengths[index] = cast(s32) arg.type.runtime_size; 121 | param_formats[index] = 1; 122 | 123 | case .STRING; 124 | str := cast(*string) arg.value_pointer; 125 | param_types[index] = cast(Oid) Pq_Type.TEXT; 126 | param_values[index] = ifx str.data else ""; // This handles empty strings. Sending no data means null string 127 | param_lengths[index] = cast(s32) str.count; 128 | param_formats[index] = 1; 129 | 130 | case .BOOL; 131 | val := cast(*bool) arg.value_pointer; 132 | param_types[index] = cast(Oid) Pq_Type.BOOL; 133 | param_values[index] = ifx val.* then *BOOL_TRUE_VALUE else *BOOL_FALSE_VALUE; 134 | param_lengths[index] = 1; 135 | param_formats[index] = 1; 136 | 137 | // case .ARRAY; 138 | // @ToDo: Implement binary format according to array_recv format. 139 | // See https://doxygen.postgresql.org/arrayfuncs_8c.html#a315b67e6e01e8f283326b5a6b27e07c9 140 | // 141 | // Format seems to be: 142 | // 4 byte number of dimensions 143 | // 4 byte flags (seems unused, but must be 0 or 1) 144 | // 4 byte element type (sizeof OID, actually…) 145 | // Then for each dimension: 146 | // 4 byte number of elements 147 | // 4 byte lower bound 148 | // Then for each element: 149 | // 4 byte length 150 | // n byte encoded element 151 | // 152 | // But given the overhead, maybe it would be smarter to just serialize arrays as strings? 153 | // Eg. an array of two ints would be 28 bytes + 2 * int size. 154 | // The string equivalent would be 3 bytes + string-size of the integers 155 | 156 | // ToDo: Struct as Jsonb? 157 | 158 | case; 159 | // @ToDo: Implement 160 | log_error("Unsupported param type: %", arg.type.type); 161 | return false; 162 | } 163 | } 164 | 165 | c_command := to_c_string(command); 166 | 167 | result: int; 168 | result = PQsendQueryParams(conn, c_command, cast(s32) args.count, param_types.data, param_values.data, param_lengths.data, param_formats.data, 1); 169 | if result == 0 { 170 | error_message: string; 171 | error_message.data = PQerrorMessage(conn); 172 | error_message.count = c_style_strlen(error_message.data); 173 | log_error("Could not send query: %", error_message); 174 | return false; 175 | } 176 | 177 | return true; 178 | } 179 | 180 | 181 | get_last_query_result :: (conn: *PGconn) -> *PGresult { 182 | query_result: *PGresult; 183 | while true { 184 | latest_result := PQgetResult(conn); 185 | if latest_result == null break; 186 | if query_result PQclear(query_result); 187 | query_result = latest_result; 188 | } 189 | 190 | return query_result; 191 | } 192 | 193 | check_query_result :: (query_res: *PGresult) -> has_results: bool, success: bool, updated_rows: int { 194 | res_status := PQresultStatus(query_res); 195 | if res_status == { 196 | case ExecStatusType.PGRES_EMPTY_QUERY; 197 | return false, true, 0; 198 | case ExecStatusType.PGRES_COMMAND_OK; 199 | rows_affected := to_string(PQcmdTuples(query_res)); 200 | rows_affected_count := string_to_int(rows_affected); 201 | 202 | return false, true, rows_affected_count; 203 | case ExecStatusType.PGRES_TUPLES_OK; 204 | return true, true, 0; 205 | case ExecStatusType.PGRES_FATAL_ERROR; 206 | error_message := to_string(PQresultErrorMessage(query_res)); 207 | log_error("Fatal error: %", error_message); 208 | return false, false, 0; 209 | case ExecStatusType.PGRES_NONFATAL_ERROR; 210 | error_message := to_string(PQresultErrorMessage(query_res)); 211 | log_error("Non-fatal error: %", error_message); 212 | return false, false, 0; 213 | case; 214 | log_error("Query result status: %", res_status); 215 | return false, false, 0; 216 | } 217 | } 218 | 219 | get_results :: (query_res: *PGresult, $T: Type, $ignore_unknown := false) -> [] T, success: bool { 220 | raw_result, success := get_results(query_res, type_info(T), ignore_unknown); 221 | 222 | results: []T; 223 | results.data = xx raw_result.data; 224 | results.count = raw_result.count; 225 | return results, success; 226 | } 227 | 228 | Member_Offset :: struct { 229 | member: *Type_Info_Struct_Member; 230 | offset_in_bytes: s64; 231 | } 232 | 233 | get_results :: (query_res: *PGresult, info: *Type_Info, ignore_unknown := false) -> Array_View_64, success: bool { 234 | // @ToDo: Allow to cast single-column results directly to primitive types 235 | assert(info.type == .STRUCT); 236 | info_struct := cast(*Type_Info_Struct) info; 237 | 238 | num_tuples := PQntuples(query_res); 239 | results: Array_View_64; 240 | results.data = alloc(info.runtime_size * num_tuples); 241 | results.count = num_tuples; 242 | initializer := info_struct.initializer; 243 | if !initializer { 244 | memset(results.data, 0, info.runtime_size * num_tuples); 245 | } 246 | 247 | num_columns := PQnfields(query_res); 248 | column_members := NewArray(num_columns, Member_Offset, initialized = false); 249 | defer array_free(column_members); 250 | 251 | for col: 0..num_columns - 1 { 252 | name: string; 253 | name.data = PQfname(query_res, col); 254 | name.count = c_style_strlen(name.data); 255 | member, offset_in_bytes := get_field(info_struct, name); 256 | if !ignore_unknown && !member { 257 | log_error("Column \"%\" has no corresponding member in struct type \"%\"", name, < bool { 291 | if col_type == { 292 | case .INT2; 293 | assert(len == 2); 294 | // Explicitly throwing away 48 bits. 295 | val := ntoh(<< cast(*s16) data); 296 | return write_integer(col, name, info, slot, col_type, val); 297 | 298 | case .OID; #through; 299 | case .INT4; 300 | assert(len == 4); 301 | // Explicitly throwing away 32 bits. 302 | val := ntoh(<< cast(*s32) data); 303 | return write_integer(col, name, info, slot, col_type, val); 304 | 305 | case .INT8; 306 | assert(len == 8); 307 | val := ntoh(<< cast(*s64) data); 308 | return write_integer(col, name, info, slot, col_type, val); 309 | 310 | case .FLOAT4; 311 | if info.type != Type_Info_Tag.FLOAT { 312 | log_error("Error: Trying to write float4 column % into member field \"%\" of type %", col, name, info.type); 313 | return false; 314 | } 315 | 316 | if info.runtime_size == 4 { 317 | << (cast(*float32) slot) = ntoh(<<(cast(*float32) data)); 318 | } else { 319 | assert(info.runtime_size == 8); 320 | << (cast(*float64) slot) = cast(float64) ntoh(<<(cast(*float32) data)); 321 | } 322 | return true; 323 | 324 | case .FLOAT8; 325 | if info.type != Type_Info_Tag.FLOAT { 326 | log_error("Error: Trying to write float8 column % into member field \"%\" of type %", col, name, info.type); 327 | return false; 328 | } 329 | 330 | if info.runtime_size == 4 { 331 | << (cast(*float32) slot) = cast(float32) ntoh(<<(cast(*float64) data)); 332 | } else { 333 | assert(info.runtime_size == 8); 334 | << (cast(*float64) slot) = ntoh(<<(cast(*float64) data)); 335 | } 336 | return true; 337 | 338 | case .NUMERIC; 339 | if info.type != Type_Info_Tag.STRING { 340 | log_error("Error: Trying to write numeric column % into member field \"%\" of type %, but we only support writing to string fields at the moment.", col, name, info.type); 341 | return false; 342 | } 343 | success: bool; 344 | << (cast(*string) slot), success = str_from_numeric(data, len, row, col); 345 | return success; 346 | 347 | case .BOOL; 348 | assert(len == 1); 349 | if info.type != Type_Info_Tag.BOOL { 350 | log_error("Error: Trying to write bool column % into member field \"%\" of type %", col, name, info.type); 351 | return false; 352 | } 353 | 354 | val := << cast(*u8) data; 355 | << (cast(*bool) slot) = (val != 0); 356 | 357 | return true; 358 | 359 | case .CHAR; #through; 360 | case .BPCHAR; #through; 361 | case .VARCHAR; #through; 362 | case .NAME; #through; 363 | case .TEXT; 364 | val: string; 365 | val.data = data; 366 | val.count = len; 367 | write_string_value(col, name, info, slot, col_type, val, is_custom = false); 368 | return true; 369 | 370 | case .DATE; 371 | assert(len == 4); 372 | days_since_2000_01_01 := ntoh(<< cast(*s32) data); 373 | if info.type == Type_Info_Tag.INTEGER { 374 | return write_integer(col, name, info, slot, col_type, days_since_2000_01_01); 375 | } else { 376 | #if SUPPORT_DATE_AS_STRINGS { 377 | #import "date"; // https://github.com/rluba/jai-date 378 | date := Date.{2000, 1, 1}; 379 | add(*date, days_since_2000_01_01, .DAY); 380 | if info == type_info(Date) { 381 | target := cast(*Date) slot; 382 | < enum_highest_value(Pq_Type) { 460 | // Seems to be a custom type. Try to interpret it as a string 461 | val: string; 462 | val.data = data; 463 | val.count = len; 464 | return write_string_value(col, name, info, slot, col_type, val, is_custom = true); 465 | } else { 466 | log_error("Error: column % type % (with length %) is not yet implemented (for member %)", col, col_type, len, name); 467 | } 468 | return false; 469 | } 470 | } 471 | 472 | write_string_value :: (col: int, name: string, info: *Type_Info, slot: *void, col_type: Pq_Type, value: string, is_custom: bool) -> bool { 473 | if info.type == { 474 | case .STRING; 475 | << (cast(*string) slot) = copy_string(value); 476 | return true; 477 | case .ENUM; 478 | info_enum := cast(*Type_Info_Enum) info; 479 | for info_enum.names { 480 | if it == value { 481 | return write_integer(col, name, info_enum.internal_type, slot, col_type, info_enum.values[it_index]); 482 | } 483 | } 484 | 485 | log_error("Error: Could not find an enum value for value \"%\" of column % for member field \"%\"", value, col, name); 486 | return false; 487 | case; 488 | if is_custom { 489 | log_error("Error: Trying to write custom type of column % into member field \"%\" of type %", col, name, info.type); 490 | } else { 491 | log_error("Error: Trying to write column % of type % into member field \"%\" of type %", col, col_type, name, info.type); 492 | } 493 | return false; 494 | } 495 | } 496 | 497 | write_integer :: (col: int, name: string, info: *Type_Info, pointer: *void, col_type: Pq_Type, value: s64) -> bool { 498 | if info.type != Type_Info_Tag.INTEGER { 499 | log_error("Error: Trying to write column % of type % as an integer into member field \"%\" of type %", col, col_type, name, info.type); 500 | return false; 501 | } 502 | int_info := cast(*Type_Info_Integer) info; 503 | 504 | if int_info.signed { 505 | valid, low, high := Reflection.range_check_and_store(value, int_info, pointer); 506 | 507 | if !valid { 508 | log_error("Value % of column % is out of range for \"%\". (The value must be between % and %.)", value, col, name, low, high); 509 | return false; 510 | } 511 | } else { 512 | valid, low, high := Reflection.range_check_and_store(cast(u64) value, int_info, pointer); // Different overload from the above! 513 | 514 | if !valid { 515 | log_error("Value % of column % is out of range for \"%\". (The value must be between % and %.)", value, col, name, low, high); 516 | return false; 517 | } 518 | } 519 | 520 | return true; 521 | } 522 | 523 | str_from_numeric :: (data: *u8, len: int, row: int, col: int) -> string, success: bool { 524 | if len < 8 { 525 | log_error("Invalid numeric length at row % col %: %", row, col, len); 526 | return "", false; 527 | } 528 | 529 | num_digits := ntoh(<< cast(*u16) data); 530 | weight := ntoh(<< cast(*s16) (data + 2)); 531 | sign := ntoh(<< cast(*u16) (data + 4)); 532 | dscale := ntoh(<< cast(*u16) (data + 6)); 533 | if len != num_digits * 2 + 8 { 534 | log_error("Invalid numeric length at row % col %: %", row, col, len); 535 | return "", false; 536 | } 537 | if dscale > 0x3fff { 538 | log_error("Invalid numeric dscale at row % col %: %", row, col, dscale); 539 | return "", false; 540 | } 541 | 542 | builder: String_Builder; 543 | defer free_buffers(*builder); 544 | 545 | if sign == { 546 | case 0x0000; 547 | 548 | case 0x4000; 549 | append(*builder, "-"); 550 | 551 | case 0xc000; 552 | append(*builder, "NaN"); 553 | 554 | case; 555 | log_error("Invalid numeric sign at row % col%: %", row, col, sign); 556 | return "", false; 557 | } 558 | 559 | index := 0; 560 | if weight >= 0 && num_digits { 561 | min_digits := 1; 562 | while weight >= 0 && index < num_digits { 563 | digit := ntoh(<< cast(*s16) (data + 8 + index * 2)); 564 | print_to_builder(*builder, "%", formatInt(digit, minimum_digits=min_digits)); 565 | min_digits = 4; 566 | weight -= 1; 567 | index += 1; 568 | } 569 | while weight >= 0 { 570 | append(*builder, "0000"); 571 | weight -= 1; 572 | } 573 | } else { 574 | append(*builder, "0"); 575 | } 576 | 577 | if dscale != 0 append(*builder, "."); 578 | dec_len := 0; 579 | 580 | omitted := -1 - weight; 581 | if omitted > 0 { 582 | zeros: int; 583 | if 4 * omitted > cast(s32) dscale { 584 | zeros = dscale; 585 | } else { 586 | zeros = 4 * omitted; 587 | } 588 | print_to_builder(*builder, "%", formatInt(0, minimum_digits=zeros)); 589 | dec_len += zeros; 590 | } 591 | min_digits := 4; 592 | while index < num_digits { 593 | digit := ntoh(<< cast(*s16) (data + 8 + index * 2)); 594 | if dec_len + 4 > dscale { 595 | remaining := dscale - dec_len; 596 | assert(remaining > 0, "Unexpected number of remaining digits: %", remaining); 597 | for 1..(4 - remaining) digit /= 10; 598 | min_digits = remaining; 599 | } 600 | print_to_builder(*builder, "%", formatInt(digit, minimum_digits=min_digits)); 601 | index += 1; 602 | dec_len += min_digits; 603 | } 604 | if (dec_len < dscale) { 605 | print_to_builder(*builder, "%", formatInt(0, minimum_digits=dscale-dec_len)); 606 | } 607 | 608 | return builder_to_string(*builder), true; 609 | } 610 | 611 | BOOL_TRUE_VALUE: u8 = 1; 612 | BOOL_FALSE_VALUE: u8 = 0; 613 | 614 | #import "Basic"; 615 | #import "Pool"; 616 | Reflection :: #import "Reflection"; 617 | 618 | #load "byte_order.jai"; 619 | #load "pq_types.jai"; 620 | 621 | #if OS == .WINDOWS { 622 | Windows :: #import "Windows"; 623 | #load "bindings/windows.jai"; 624 | size_t :: Windows.size_t; 625 | FILE :: *void; 626 | } else { 627 | #import "POSIX"; 628 | #load "bindings/unix.jai"; 629 | } 630 | -------------------------------------------------------------------------------- /pq_types.jai: -------------------------------------------------------------------------------- 1 | // Generated via "generate_types.jai" 2 | 3 | Pq_Type :: enum Oid { 4 | BOOL :: 16; 5 | BYTEA :: 17; 6 | CHAR :: 18; 7 | NAME :: 19; 8 | INT8 :: 20; 9 | INT2 :: 21; 10 | INT2VECTOR :: 22; 11 | INT4 :: 23; 12 | REGPROC :: 24; 13 | TEXT :: 25; 14 | OID :: 26; 15 | TID :: 27; 16 | XID :: 28; 17 | CID :: 29; 18 | OIDVECTOR :: 30; 19 | JSON :: 114; 20 | XML :: 142; 21 | PG_NODE_TREE :: 194; 22 | POINT :: 600; 23 | LSEG :: 601; 24 | PATH :: 602; 25 | BOX :: 603; 26 | POLYGON :: 604; 27 | LINE :: 628; 28 | CIDR :: 650; 29 | FLOAT4 :: 700; 30 | FLOAT8 :: 701; 31 | CIRCLE :: 718; 32 | MACADDR8 :: 774; 33 | MONEY :: 790; 34 | MACADDR :: 829; 35 | INET :: 869; 36 | ACLITEM :: 1033; 37 | BPCHAR :: 1042; 38 | VARCHAR :: 1043; 39 | DATE :: 1082; 40 | TIME :: 1083; 41 | TIMESTAMP :: 1114; 42 | TIMESTAMPTZ :: 1184; 43 | INTERVAL :: 1186; 44 | TIMETZ :: 1266; 45 | BIT :: 1560; 46 | VARBIT :: 1562; 47 | NUMERIC :: 1700; 48 | REFCURSOR :: 1790; 49 | REGPROCEDURE :: 2202; 50 | REGOPER :: 2203; 51 | REGOPERATOR :: 2204; 52 | REGCLASS :: 2205; 53 | REGTYPE :: 2206; 54 | UUID :: 2950; 55 | TXID_SNAPSHOT :: 2970; 56 | PG_LSN :: 3220; 57 | PG_NDISTINCT :: 3361; 58 | PG_DEPENDENCIES :: 3402; 59 | TSVECTOR :: 3614; 60 | TSQUERY :: 3615; 61 | GTSVECTOR :: 3642; 62 | REGCONFIG :: 3734; 63 | REGDICTIONARY :: 3769; 64 | JSONB :: 3802; 65 | JSONPATH :: 4072; 66 | REGNAMESPACE :: 4089; 67 | REGROLE :: 4096; 68 | REGCOLLATION :: 4191; 69 | PG_MCV_LIST :: 5017; 70 | PG_SNAPSHOT :: 5038; 71 | XID8 :: 5069; 72 | } 73 | 74 | Pq_Array_Type :: enum Oid { 75 | BOOL :: 1000; 76 | BYTEA :: 1001; 77 | CHAR :: 1002; 78 | NAME :: 1003; 79 | INT8 :: 1016; 80 | INT2 :: 1005; 81 | INT2VECTOR :: 1006; 82 | INT4 :: 1007; 83 | REGPROC :: 1008; 84 | TEXT :: 1009; 85 | OID :: 1028; 86 | TID :: 1010; 87 | XID :: 1011; 88 | CID :: 1012; 89 | OIDVECTOR :: 1013; 90 | JSON :: 199; 91 | XML :: 143; 92 | POINT :: 1017; 93 | LSEG :: 1018; 94 | PATH :: 1019; 95 | BOX :: 1020; 96 | POLYGON :: 1027; 97 | LINE :: 629; 98 | CIDR :: 651; 99 | FLOAT4 :: 1021; 100 | FLOAT8 :: 1022; 101 | CIRCLE :: 719; 102 | MACADDR8 :: 775; 103 | MONEY :: 791; 104 | MACADDR :: 1040; 105 | INET :: 1041; 106 | ACLITEM :: 1034; 107 | BPCHAR :: 1014; 108 | VARCHAR :: 1015; 109 | DATE :: 1182; 110 | TIME :: 1183; 111 | TIMESTAMP :: 1115; 112 | TIMESTAMPTZ :: 1185; 113 | INTERVAL :: 1187; 114 | TIMETZ :: 1270; 115 | BIT :: 1561; 116 | VARBIT :: 1563; 117 | NUMERIC :: 1231; 118 | REFCURSOR :: 2201; 119 | REGPROCEDURE :: 2207; 120 | REGOPER :: 2208; 121 | REGOPERATOR :: 2209; 122 | REGCLASS :: 2210; 123 | REGTYPE :: 2211; 124 | UUID :: 2951; 125 | TXID_SNAPSHOT :: 2949; 126 | PG_LSN :: 3221; 127 | TSVECTOR :: 3643; 128 | TSQUERY :: 3645; 129 | GTSVECTOR :: 3644; 130 | REGCONFIG :: 3735; 131 | REGDICTIONARY :: 3770; 132 | JSONB :: 3807; 133 | JSONPATH :: 4073; 134 | REGNAMESPACE :: 4090; 135 | REGROLE :: 4097; 136 | REGCOLLATION :: 4192; 137 | PG_SNAPSHOT :: 5039; 138 | XID8 :: 271; 139 | } 140 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mimimal Postgresql client for Jai 2 | 3 | This module contains `libpq` bindings as well as some higher-level functions for… 4 | * … connecting to a Postgresql database, 5 | * … executing parameterized queries and 6 | * … optionally parsing the results into a typed array. 7 | 8 | It only supports synchronous execution for now. 9 | 10 | ## Usage 11 | 12 | First connect to a Postgresql instance using a connection uri string: 13 | 14 | ```Jai 15 | connection_str := "postgres://:@:/"; 16 | conn, success := connect(connection_str); 17 | defer disconnect(conn); 18 | ``` 19 | 20 | Then use `conn` to execute statements… 21 | 22 | ```Jai 23 | // Restrict schema access 24 | success = execute(conn, "SELECT pg_catalog.set_config('search_path', 'public', false)"); 25 | ``` 26 | 27 | … or execute a query and automatically parse the result into an array of any given type: 28 | 29 | ```Jai 30 | query :: #string END 31 | SELECT * FROM tourist_attractions 32 | WHERE city = $1 AND price < $2 AND min_age < $3 33 | ORDER BY name 34 | END 35 | 36 | Attraction :: struct { 37 | name: string; 38 | city: string; 39 | price: string; // Numerics are parsed into strings 40 | min_age: int; 41 | is_open: bool; 42 | } 43 | 44 | // Query parameters can be passed as variadic args after `query`: 45 | city := "Vienna"; 46 | price := 50; 47 | min_age := 18; 48 | attractions, success = execute(conn, Attraction, query, city, price, min_age); 49 | ``` 50 | 51 | By default, `execute` fails if the result contains a column that has no corresponding member in the struct type you passed. 52 | You can pass `ignore_unknown = true` to `execute(…)` to ignore unknown columns instead. 53 | 54 | If your struct type contains members that aren’t present in the result (or are null), they will be left at their default values. 55 | 56 | ## Memory model 57 | 58 | You’re responsible for freeing everything (including strings) in the result array. 59 | Using a Pool allocator around your `execute` calls might be a good idea. 60 | 61 | ## Supported query parameter types 62 | 63 | * All integer types 64 | * `float` and `float64` 65 | * `string` 66 | * `bool` 67 | 68 | ## Supported return value column types 69 | 70 | * `INT2`, `INT4`, `INT8` 71 | * `FLOAT4`, `FLOAT8` 72 | * `NUMERIC` (currently only parsed into `string` fields) 73 | * `BOOL` 74 | * `CHAR`, `BPCHAR`, `VARCHAR`, `NAME`, `TEXT` 75 | * `DATE`, `TIMESTAMP`, `TIMESTAMPTZ` 76 | * `BYTEA` 77 | * `OID` 78 | * `UUID` 79 | * Custom enum types (`CREATE TYPE … AS ENUM`), which can be parsed into `string` or `enum` member fields. 80 | --------------------------------------------------------------------------------