├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── Makefile ├── README.md ├── binding.gyp ├── index.js ├── package-lock.json ├── package.json ├── src ├── addon.cc ├── addon.h ├── connect-async-worker.cc ├── connect-async-worker.h ├── connection.cc └── connection.h ├── test ├── async-connection.js ├── async-socket.js ├── cancel.js ├── construction.js ├── copy-in.js ├── copy-out.js ├── error-conditions.js ├── error-info.js ├── escaping.js ├── helper.js ├── index.js ├── load.js ├── many-connections.js ├── mocha.opts ├── multiple-queries.js ├── non-blocking-controls.js ├── notification.js ├── result-accessors.js ├── server-version.js ├── socket.js ├── sync-integration.js ├── sync-parameters.js ├── sync-prepare.js └── sync.js └── vendor ├── build.sh ├── install_libpq.sh └── install_openssl.sh /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | services: 13 | postgres: 14 | image: postgres:11 15 | env: 16 | POSTGRES_USER: postgres 17 | POSTGRES_PASSWORD: postgres 18 | POSTGRES_DB: ci_db_test 19 | ports: 20 | - 5432:5432 21 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 22 | 23 | strategy: 24 | matrix: 25 | node-version: [18.x, 20.x, 22.x, 24.x] 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v3 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | - run: npm ci 34 | - run: npm run build --if-present 35 | - run: PGTESTNOSSL=true PGUSER=postgres PGHOST=localhost PGPASSWORD=postgres PGDATABASE=ci_db_test npm test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | _site 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: publish-patch test 2 | 3 | test: 4 | npm test 5 | 6 | patch: test 7 | npm version patch -m "Bump version" 8 | git push origin master --tags 9 | npm publish 10 | 11 | minor: test 12 | npm version minor -m "Bump version" 13 | git push origin master --tags 14 | npm publish 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-libpq 2 | 3 | [![Build Status](https://travis-ci.org/brianc/node-libpq.svg?branch=master)](https://travis-ci.org/brianc/node-libpq) 4 | 5 | Node native bindings to the PostgreSQL [libpq](http://www.postgresql.org/docs/9.3/interactive/libpq.html) C client library. This module attempts to mirror _as closely as possible_ the C API provided by libpq and provides the absolute minimum level of abstraction. It is intended to be extremely low level and allow you the same access as you would have to libpq directly from C, except in node.js! The obvious trade-off for being "close to the metal" is having to use a very "c style" API in JavaScript. 6 | 7 | If you have a good understanding of libpq or used it before hopefully the methods within node-libpq will be familiar; otherwise, you should probably spend some time reading [the official libpq C library documentation](http://www.postgresql.org/docs/9.3/interactive/libpq.html) to become a bit familiar. Referencing the libpq documentation directly should also provide you with more insight into the methods here. I will do my best to explain any differences from the C code for each method. 8 | 9 | I am also building some [higher level abstractions](https://github.com/brianc/node-pg-native) to eventually replace the `pg.native` portion of node-postgres. They should help as reference material. 10 | 11 | This module relies heavily on [nan](https://github.com/rvagg/nan) and wouldn't really be possible without it. Mucho thanks to the node-nan team. 12 | 13 | ## install 14 | 15 | You need libpq installed & the `pg_config` program should be in your path. You also need [node-gyp](https://github.com/TooTallNate/node-gyp) installed. 16 | 17 | ```bash 18 | $ npm install libpq 19 | ``` 20 | 21 | > Note: for Node.js equal or greater to version 10.16.0 you need to have at least `OpenSSL 1.1.1` installed. 22 | 23 | ## use 24 | 25 | ```js 26 | var Libpq = require('libpq'); 27 | var pq = new Libpq(); 28 | ``` 29 | 30 | ## API 31 | 32 | ### connection functions 33 | 34 | Libpq provides a few different connection functions, some of which are "not preferred" anymore. I've opted to simplify this interface a bit into a single __async__ and single __sync__ connnection function. The function accepts an connection string formatted as outlined [in this documentation in section 31.1.1](http://www.postgresql.org/docs/9.3/static/libpq-connect.html). If the parameters are not supplied, libpq will automatically use environment variables, a pgpass file, and other options. Consult the libpq documentation for a better rundown of all the ways it tries to determine your connection parameters. 35 | 36 | I personally __always__ connect with environment variables and skip supplying the optional `connectionParams`. Easier, more 12 factor app-ish, and you never risk hard coding any passwords. YMMV. :smile: 37 | 38 | ##### `pq.connect([connectionParams:string], callback:function)` 39 | 40 | Asyncronously attempts to connect to the postgres server. 41 | 42 | - `connectionParams` is an optional string 43 | - `callback` is mandatory. It is called when the connection has successfully been established. 44 | 45 | __async__ Connects to a PostgreSQL backend server process. 46 | 47 | This function actually calls the `PQconnectdb` blocking connection method in a background thread within node's internal thread-pool. There is a way to do non-blocking network I/O for some of the connecting with libpq directly, but it still blocks when your local file system looking for config files, SSL certificates, .pgpass file, and doing possible dns resolution. Because of this, the best way to get _fully_ non-blocking is to juse use `libuv_queue_work` and let node do it's magic and so that's what I do. This function _does not block_. 48 | 49 | ##### `pq.connectSync([connectionParams:string])` 50 | 51 | Attempts to connect to a PostgreSQL server. __BLOCKS__ until it either succeedes, or fails. If it fails it will throw an exception. 52 | 53 | - `connectionParams` is an optional string 54 | 55 | ##### `pq.finish()` 56 | 57 | Disconnects from the backend and cleans up all memory used by the libpq connection. 58 | 59 | ### Connection Status Functions 60 | 61 | ##### `pq.errorMessage():string` 62 | 63 | Retrieves the last error message from the connection. This is intended to be used after most functions which return an error code to get more detailed error information about the connection. You can also check this _before_ issuing queries to see if your connection has been lost. 64 | 65 | ##### `pq.socket():int` 66 | 67 | Returns an int representing the file descriptor for the socket used internally by the connection 68 | 69 | ### Sync Command Execution Functions 70 | 71 | ##### `pq.exec(commandText:string)` 72 | 73 | __sync__ sends a command to the backend and blocks until a result is received. 74 | 75 | - `commandText` is a required string of the query. 76 | 77 | ##### `pq.execParams(commandText:string, parameters:array[string])` 78 | 79 | __snyc__ sends a command and parameters to the backend and blocks until a result is received. 80 | 81 | - `commandText` is a required string of the query. 82 | - `parameters` is a required array of string values corresponding to each parameter in the commandText. 83 | 84 | ##### `pq.prepare(statementName:string, commandText:string, nParams:int)` 85 | __sync__ sends a named statement to the server to be prepared for later execution. blocks until a result from the prepare operation is received. 86 | 87 | - `statementName` is a required string of name of the statement to prepare. 88 | - `commandText` is a required string of the query. 89 | - `nParams` is a count of the number of parameters in the commandText. 90 | 91 | ##### `pq.execPrepared(statementName:string, parameters:array[string])` 92 | __sync__ sends a command to the server to execute a previously prepared statement. blocks until the results are returned. 93 | 94 | - `statementName` is a required string of the name of the prepared statement. 95 | - `parameters` are the parameters to pass to the prepared statement. 96 | 97 | ##### `pq.describePrepared(statementName:string)` 98 | __sync__ sends a command to the server to describe a previously prepared statement. blocks until the results are returned. Use `pq.nparams` and `pq.paramtype` to obtain information about the parameters and `pq.nfields`, `pq.fname` and `pq.ftype` about the result columns of the prepared statement. 99 | 100 | ### Async Command Execution Functions 101 | 102 | In libpq the async command execution functions _only_ dispatch a request to the backend to run a query. They do not start result fetching on their own. Because libpq is a C api there is a somewhat complicated "dance" to retrieve the result information in a non-blocking way. node-libpq attempts to do as little as possible to abstract over this; therefore, the following functions are only part of the story. For a complete tutorial on how to dispatch & retrieve results from libpq in an async way you can [view the complete approach here](https://github.com/brianc/node-pg-native/blob/master/index.js#L105) 103 | 104 | ##### `pq.sendQuery(commandText:string):boolean` 105 | __async__ sends a query to the server to be processed. 106 | 107 | - `commandText` is a required string containing the query text. 108 | 109 | Returns `true` if the command was sent succesfully or `false` if it failed to send. 110 | 111 | ##### `pq.sendQueryParams(commandText:string, parameters:array[string]):boolean` 112 | __async__ sends a query and to the server to be processed. 113 | 114 | - `commandText` is a required string containing the query text. 115 | - `parameters` is an array of parameters as strings used in the parameterized query. 116 | 117 | Returns `true` if the command was sent succesfully or `false` if it failed to send. 118 | 119 | ##### `pq.sendPrepare(statementName:string, commandText:string, nParams:int):boolean` 120 | __async__ sends a request to the backend to prepare a named statement with the given name. 121 | 122 | - `statementName` is a required string of name of the statement to prepare. 123 | - `commandText` is a required string of the query. 124 | - `nParams` is a count of the number of parameters in the commandText. 125 | 126 | Returns `true` if the command was sent succesfully or `false` if it failed to send. 127 | 128 | ##### `pq.sendQueryPrepared(statementName:string, parameters:array[string]):boolean` 129 | __async__ sends a request to execute a previously prepared statement. 130 | 131 | - `statementName` is a required string of the name of the prepared statement. 132 | - `parameters` are the parameters to pass to the prepared statement. 133 | 134 | ##### `pq.getResult():boolean` 135 | Parses received data from the server into a `PGresult` struct and sets a pointer internally to the connection object to this result. __warning__: this function will __block__ if libpq is waiting on async results to be returned from the server. Call `pq.isBusy()` to determine if this command will block. 136 | 137 | Returns `true` if libpq was able to read buffered data & parse a result object. Returns `false` if there are no results waiting to be parsed. Generally doing async style queries you'll call this repeadedly until it returns false and then use the result accessor methods to pull results out of the current result set. 138 | 139 | ### Result accessor functions 140 | 141 | After a command is run in either sync or async mode & the results have been received, node-libpq stores the results internally and provides you access to the results via the standard libpq methods. The difference here is libpq will return a pointer to a PGresult structure which you access via libpq functions, but node-libpq stores the most recent result within itself and passes the opaque PGresult structure to the libpq methods. This is to avoid passing around a whole bunch of pointers to unmanaged memory and keeps the burden of properly allocating and freeing memory within node-libpq. 142 | 143 | ##### `pq.resultStatus():string` 144 | 145 | Returns either `PGRES_COMMAND_OK` or `PGRES_FATAL_ERROR` depending on the status of the last executed command. 146 | 147 | ##### `pq.resultErrorMessage():string` 148 | 149 | Retrieves the error message from the result. This will return `null` if the result does not have an error. 150 | 151 | ##### `pq.resultErrorFields():object` 152 | 153 | Retrieves detailed error information from the current result object. Very similar to `PQresultErrorField()` except instead of passing a fieldCode and retrieving a single field, retrieves all fields from the error at once on a single object. The object returned is a simple hash, _not_ an instance of an error object. Example: if you wanted to access `PG_DIAG_MESSAGE_DETAIL` you would do the following: 154 | 155 | ```js 156 | console.log(pq.errorFields().messageDetail) 157 | ``` 158 | 159 | ##### `pq.clear()` 160 | 161 | Manually frees the memory associated with a `PGresult` pointer. Generally this is called for you, but if you absolutely want to free the pointer yourself, you can. 162 | 163 | ##### `pq.ntuples():int` 164 | 165 | Retrieve the number of tuples (rows) from the result. 166 | 167 | ##### `pq.nfields():int` 168 | 169 | Retrieve the number of fields (columns) from the result. 170 | 171 | ##### `pq.fname(fieldNumber:int):string` 172 | 173 | Retrieve the name of the field (column) at the given offset. Offset starts at 0. 174 | 175 | ##### `pq.ftable(fieldNumber:int):int` 176 | 177 | Retrieve the `Oid` of the table at the given offset. Offset starts at 0. 178 | 179 | ##### `pq.ftablenum(fieldNumber:int):int` 180 | 181 | Retrieve the column number (within its table) of the field at the given offset. Offset starts at 0. 182 | Query-result column numbers start at 0, but table columns have nonzero numbers. 183 | 184 | ##### `pq.ftype(fieldNumber:int):int` 185 | 186 | Retrieve the `Oid` of the field (column) at the given offset. Offset starts at 0. 187 | 188 | ##### `pq.getvalue(tupleNumber:int, fieldNumber:int):string` 189 | 190 | Retrieve the text value at a given tuple (row) and field (column) offset. Both offsets start at 0. A null value is returned as the empty string `''`. 191 | 192 | ##### `pq.getisnull(tupleNumber:int, fieldNumber:int):boolean` 193 | 194 | Returns `true` if the value at the given offsets is actually `null`. Otherwise returns `false`. This is because `pq.getvalue()` returns an empty string for both an actual empty string and for a `null` value. Weird, huh? 195 | 196 | ##### `pq.nparams():int` 197 | 198 | Returns the number of parameters a prepared statement expects. 199 | 200 | ##### `pq.paramtype(paramNumber:int):int` 201 | 202 | Returns the `Oid` of the prepared statement's parameter at the given offset. 203 | 204 | ##### `pq.cmdStatus():string` 205 | 206 | Returns the status string associated with a result. Something akin to `INSERT 3 0` if you inserted 3 rows. 207 | 208 | ##### `pq.cmdTuples():string` 209 | 210 | Returns the number of tuples (rows) affected by the command. Even though this is a number, it is returned as a string to mirror libpq's behavior. 211 | 212 | ### Async socket access 213 | 214 | These functions don't have a direct match within libpq. They exist to allow you to monitor the readability or writability of the libpq socket based on your platforms equivilant to `select()`. This allows you to perform async I/O completely from JavaScript. 215 | 216 | ##### `pq.startReader()` 217 | 218 | This uses libuv to start a read watcher on the socket open to the backend. As soon as this socket becomes readable the `pq` instance will emit a `readable` event. It is up to you to call `pq.consumeInput()` one or more times to clear this read notification or it will continue to emit read events over and over and over. The exact flow is outlined [here] under the documentation for `PQisBusy`. 219 | 220 | ##### `pq.stopReader()` 221 | 222 | Tells libuv to stop the read watcher on the connection socket. 223 | 224 | ##### `pq.writable(callback:function)` 225 | 226 | Call this to make sure the socket has flushed all data to the operating system. Once the socket is writable, your callback will be called. Usefully when using `PQsetNonBlocking` and `PQflush` for async writing. 227 | 228 | ### More async methods 229 | 230 | These are all documented in detail within the [libpq documentation](http://www.postgresql.org/docs/9.3/static/libpq-async.html) and function almost identically. 231 | 232 | ##### `pq.consumeInput():boolean` 233 | 234 | Reads waiting data from the socket. If the socket is not readable and you call this it will __block__ so be careful and only call it within the `readable` callback for the most part. 235 | 236 | Returns `true` if data was read. Returns `false` if there was an error. You can access error details with `pq.errorMessage()`. 237 | 238 | ##### `pq.isBusy():boolean` 239 | 240 | Returns `true` if calling `pq.consumeInput()` would block waiting for more data. Returns `false` if all data has been read from the socket. Once this returns `false` it is safe to call `pq.getResult()` 241 | 242 | ##### `pq.setNonBlocking(nonBlocking:boolean):boolean` 243 | 244 | Toggle the socket blocking on _write_. Returns `true` if the socket's state was succesfully toggled. Returns `false` if there was an error. 245 | 246 | - `nonBlocking` is `true` to set the connection to use non-blocking writes. `false` to use blocking writes. 247 | 248 | ##### `pq.flush():int` 249 | 250 | Flushes buffered data to the socket. Returns `1` if socket is not write-ready at which case you should call `pq.writable` with a callback and wait for the socket to be writable and then call `pq.flush()` again. Returns `0` if all data was flushed. Returns `-1` if there was an error. 251 | 252 | ### listen/notify 253 | 254 | ##### `pq.notifies():object` 255 | 256 | Checks for `NOTIFY` messages that have come in. If any have been received they will be in the following format: 257 | 258 | ```js 259 | var msg = { 260 | relname: 'name of channel', 261 | extra: 'message passed to notify command', 262 | be_pid: 130 263 | } 264 | ``` 265 | 266 | ### COPY IN/OUT 267 | 268 | ##### `pq.putCopyData(buffer:Buffer):int` 269 | 270 | After issuing a successful command like `COPY table FROM stdin` you can start putting buffers directly into the databse with this function. 271 | 272 | - `buffer` Is a required node buffer of text data such as `Buffer('column1\tcolumn2\n')` 273 | 274 | Returns `1` if sent succesfully. Returns `0` if the command would block (only if you have called `pq.setNonBlocking(true)`). Returns `-1` if there was an error sending the command. 275 | 276 | ##### `pq.putCopyEnd([errorMessage:string])` 277 | 278 | Signals the backed your copy procedure is complete. If you pass `errorMessage` it will be sent to the backend and effectively cancel the copy operation. 279 | 280 | - `errorMessage` is an _optional_ string you can pass to cancel the copy operation. 281 | 282 | Returns `1` if sent succesfully. Returns `0` if the command would block (only if you have called `pq.setNonBlocking(true)`). Returns `-1` if there was an error sending the command. 283 | 284 | 285 | ##### `pq.getCopyData(async:boolean):Buffer or int` 286 | 287 | After issuing a successfuly command like `COPY table TO stdout` gets copy data from the connection. 288 | 289 | Returns a node buffer if there is data available. 290 | 291 | Returns `0` if the copy is still in progress (only if you have called `pq.setNonBlocking(true)`). Returns `-1` if the copy is completed. Returns `-2` if there was an error. 292 | 293 | - `async` is a boolean. Pass `false` to __block__ waiting for data from the backend. _defaults to `false`_ 294 | 295 | ### Misc Functions 296 | 297 | ##### `pq.escapeLiteral(input:string):string` 298 | 299 | Exact copy of the `PQescapeLiteral` function within libpq. Requires an established connection but does not perform any I/O. 300 | 301 | ##### `pq.escapeIdentifier(input:string):string` 302 | 303 | Exact copy of the `PQescapeIdentifier` function within libpq. Requires an established connection but does not perform any I/O. 304 | 305 | ##### `pq.cancel():true -or- string` 306 | 307 | Issues a request to cancel the currently executing query _on this instance of libpq_. Returns `true` if the cancel request was sent. Returns a `string` error message if the cancel request failed for any reason. The string will contain the error message provided by libpq. 308 | 309 | ##### `pq.serverVersion():number` 310 | 311 | Returns the version of the connected PostgreSQL backend server as a number. 312 | 313 | ## testing 314 | 315 | ```sh 316 | $ npm test 317 | ``` 318 | 319 | To run the tests you need a PostgreSQL backend reachable by typing `psql` with no connection parameters in your terminal. The tests use [environment variables](http://www.postgresql.org/docs/9.3/static/libpq-envars.html) to connect to the backend. 320 | 321 | An example of supplying a specific host the tests: 322 | 323 | ```sh 324 | $ PGHOST=blabla.mydatabasehost.com npm test 325 | ``` 326 | 327 | 328 | ## license 329 | 330 | The MIT License (MIT) 331 | 332 | Copyright (c) 2014 Brian M. Carlson 333 | 334 | Permission is hereby granted, free of charge, to any person obtaining a copy 335 | of this software and associated documentation files (the "Software"), to deal 336 | in the Software without restriction, including without limitation the rights 337 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 338 | copies of the Software, and to permit persons to whom the Software is 339 | furnished to do so, subject to the following conditions: 340 | 341 | The above copyright notice and this permission notice shall be included in 342 | all copies or substantial portions of the Software. 343 | 344 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 345 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 346 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 347 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 348 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 349 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 350 | THE SOFTWARE. 351 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'conditions': [ 3 | ['OS=="linux"', { 4 | 'variables' : { 5 | # Find the pull path to the pg_config command, since iy may not be on the PATH 6 | 'pgconfig': '=6" 31 | } 32 | }, 33 | "node_modules/ansi-regex": { 34 | "version": "5.0.1", 35 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 36 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 37 | "dev": true, 38 | "engines": { 39 | "node": ">=8" 40 | } 41 | }, 42 | "node_modules/ansi-styles": { 43 | "version": "4.3.0", 44 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 45 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 46 | "dev": true, 47 | "dependencies": { 48 | "color-convert": "^2.0.1" 49 | }, 50 | "engines": { 51 | "node": ">=8" 52 | }, 53 | "funding": { 54 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 55 | } 56 | }, 57 | "node_modules/anymatch": { 58 | "version": "3.1.3", 59 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 60 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 61 | "dev": true, 62 | "dependencies": { 63 | "normalize-path": "^3.0.0", 64 | "picomatch": "^2.0.4" 65 | }, 66 | "engines": { 67 | "node": ">= 8" 68 | } 69 | }, 70 | "node_modules/argparse": { 71 | "version": "2.0.1", 72 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 73 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 74 | "dev": true 75 | }, 76 | "node_modules/async": { 77 | "version": "2.6.4", 78 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", 79 | "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", 80 | "dev": true, 81 | "dependencies": { 82 | "lodash": "^4.17.14" 83 | } 84 | }, 85 | "node_modules/balanced-match": { 86 | "version": "1.0.2", 87 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 88 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 89 | "dev": true 90 | }, 91 | "node_modules/binary-extensions": { 92 | "version": "2.3.0", 93 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 94 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 95 | "dev": true, 96 | "engines": { 97 | "node": ">=8" 98 | }, 99 | "funding": { 100 | "url": "https://github.com/sponsors/sindresorhus" 101 | } 102 | }, 103 | "node_modules/bindings": { 104 | "version": "1.5.0", 105 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 106 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 107 | "dependencies": { 108 | "file-uri-to-path": "1.0.0" 109 | } 110 | }, 111 | "node_modules/brace-expansion": { 112 | "version": "2.0.1", 113 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 114 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 115 | "dev": true, 116 | "dependencies": { 117 | "balanced-match": "^1.0.0" 118 | } 119 | }, 120 | "node_modules/braces": { 121 | "version": "3.0.3", 122 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 123 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 124 | "dev": true, 125 | "dependencies": { 126 | "fill-range": "^7.1.1" 127 | }, 128 | "engines": { 129 | "node": ">=8" 130 | } 131 | }, 132 | "node_modules/browser-stdout": { 133 | "version": "1.3.1", 134 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 135 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 136 | "dev": true 137 | }, 138 | "node_modules/buffer-from": { 139 | "version": "1.1.2", 140 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 141 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 142 | "dev": true 143 | }, 144 | "node_modules/camelcase": { 145 | "version": "6.3.0", 146 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 147 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 148 | "dev": true, 149 | "engines": { 150 | "node": ">=10" 151 | }, 152 | "funding": { 153 | "url": "https://github.com/sponsors/sindresorhus" 154 | } 155 | }, 156 | "node_modules/chalk": { 157 | "version": "4.1.2", 158 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 159 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 160 | "dev": true, 161 | "dependencies": { 162 | "ansi-styles": "^4.1.0", 163 | "supports-color": "^7.1.0" 164 | }, 165 | "engines": { 166 | "node": ">=10" 167 | }, 168 | "funding": { 169 | "url": "https://github.com/chalk/chalk?sponsor=1" 170 | } 171 | }, 172 | "node_modules/chalk/node_modules/supports-color": { 173 | "version": "7.2.0", 174 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 175 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 176 | "dev": true, 177 | "dependencies": { 178 | "has-flag": "^4.0.0" 179 | }, 180 | "engines": { 181 | "node": ">=8" 182 | } 183 | }, 184 | "node_modules/chokidar": { 185 | "version": "3.5.3", 186 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 187 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 188 | "dev": true, 189 | "funding": [ 190 | { 191 | "type": "individual", 192 | "url": "https://paulmillr.com/funding/" 193 | } 194 | ], 195 | "dependencies": { 196 | "anymatch": "~3.1.2", 197 | "braces": "~3.0.2", 198 | "glob-parent": "~5.1.2", 199 | "is-binary-path": "~2.1.0", 200 | "is-glob": "~4.0.1", 201 | "normalize-path": "~3.0.0", 202 | "readdirp": "~3.6.0" 203 | }, 204 | "engines": { 205 | "node": ">= 8.10.0" 206 | }, 207 | "optionalDependencies": { 208 | "fsevents": "~2.3.2" 209 | } 210 | }, 211 | "node_modules/cliui": { 212 | "version": "7.0.4", 213 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 214 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 215 | "dev": true, 216 | "dependencies": { 217 | "string-width": "^4.2.0", 218 | "strip-ansi": "^6.0.0", 219 | "wrap-ansi": "^7.0.0" 220 | } 221 | }, 222 | "node_modules/color-convert": { 223 | "version": "2.0.1", 224 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 225 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 226 | "dev": true, 227 | "dependencies": { 228 | "color-name": "~1.1.4" 229 | }, 230 | "engines": { 231 | "node": ">=7.0.0" 232 | } 233 | }, 234 | "node_modules/color-name": { 235 | "version": "1.1.4", 236 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 237 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 238 | "dev": true 239 | }, 240 | "node_modules/debug": { 241 | "version": "4.4.0", 242 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 243 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 244 | "dev": true, 245 | "license": "MIT", 246 | "dependencies": { 247 | "ms": "^2.1.3" 248 | }, 249 | "engines": { 250 | "node": ">=6.0" 251 | }, 252 | "peerDependenciesMeta": { 253 | "supports-color": { 254 | "optional": true 255 | } 256 | } 257 | }, 258 | "node_modules/decamelize": { 259 | "version": "4.0.0", 260 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 261 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 262 | "dev": true, 263 | "engines": { 264 | "node": ">=10" 265 | }, 266 | "funding": { 267 | "url": "https://github.com/sponsors/sindresorhus" 268 | } 269 | }, 270 | "node_modules/diff": { 271 | "version": "5.2.0", 272 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", 273 | "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", 274 | "dev": true, 275 | "license": "BSD-3-Clause", 276 | "engines": { 277 | "node": ">=0.3.1" 278 | } 279 | }, 280 | "node_modules/emoji-regex": { 281 | "version": "8.0.0", 282 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 283 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 284 | "dev": true 285 | }, 286 | "node_modules/escalade": { 287 | "version": "3.1.2", 288 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", 289 | "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", 290 | "dev": true, 291 | "engines": { 292 | "node": ">=6" 293 | } 294 | }, 295 | "node_modules/escape-string-regexp": { 296 | "version": "4.0.0", 297 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 298 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 299 | "dev": true, 300 | "engines": { 301 | "node": ">=10" 302 | }, 303 | "funding": { 304 | "url": "https://github.com/sponsors/sindresorhus" 305 | } 306 | }, 307 | "node_modules/file-uri-to-path": { 308 | "version": "1.0.0", 309 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 310 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 311 | }, 312 | "node_modules/fill-range": { 313 | "version": "7.1.1", 314 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 315 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 316 | "dev": true, 317 | "dependencies": { 318 | "to-regex-range": "^5.0.1" 319 | }, 320 | "engines": { 321 | "node": ">=8" 322 | } 323 | }, 324 | "node_modules/find-up": { 325 | "version": "5.0.0", 326 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 327 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 328 | "dev": true, 329 | "dependencies": { 330 | "locate-path": "^6.0.0", 331 | "path-exists": "^4.0.0" 332 | }, 333 | "engines": { 334 | "node": ">=10" 335 | }, 336 | "funding": { 337 | "url": "https://github.com/sponsors/sindresorhus" 338 | } 339 | }, 340 | "node_modules/flat": { 341 | "version": "5.0.2", 342 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 343 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 344 | "dev": true, 345 | "bin": { 346 | "flat": "cli.js" 347 | } 348 | }, 349 | "node_modules/fs.realpath": { 350 | "version": "1.0.0", 351 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 352 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 353 | "dev": true 354 | }, 355 | "node_modules/fsevents": { 356 | "version": "2.3.3", 357 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 358 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 359 | "dev": true, 360 | "hasInstallScript": true, 361 | "optional": true, 362 | "os": [ 363 | "darwin" 364 | ], 365 | "engines": { 366 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 367 | } 368 | }, 369 | "node_modules/get-caller-file": { 370 | "version": "2.0.5", 371 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 372 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 373 | "dev": true, 374 | "engines": { 375 | "node": "6.* || 8.* || >= 10.*" 376 | } 377 | }, 378 | "node_modules/glob": { 379 | "version": "8.1.0", 380 | "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", 381 | "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", 382 | "deprecated": "Glob versions prior to v9 are no longer supported", 383 | "dev": true, 384 | "dependencies": { 385 | "fs.realpath": "^1.0.0", 386 | "inflight": "^1.0.4", 387 | "inherits": "2", 388 | "minimatch": "^5.0.1", 389 | "once": "^1.3.0" 390 | }, 391 | "engines": { 392 | "node": ">=12" 393 | }, 394 | "funding": { 395 | "url": "https://github.com/sponsors/isaacs" 396 | } 397 | }, 398 | "node_modules/glob-parent": { 399 | "version": "5.1.2", 400 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 401 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 402 | "dev": true, 403 | "dependencies": { 404 | "is-glob": "^4.0.1" 405 | }, 406 | "engines": { 407 | "node": ">= 6" 408 | } 409 | }, 410 | "node_modules/has-flag": { 411 | "version": "4.0.0", 412 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 413 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 414 | "dev": true, 415 | "engines": { 416 | "node": ">=8" 417 | } 418 | }, 419 | "node_modules/he": { 420 | "version": "1.2.0", 421 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 422 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 423 | "dev": true, 424 | "bin": { 425 | "he": "bin/he" 426 | } 427 | }, 428 | "node_modules/inflight": { 429 | "version": "1.0.6", 430 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 431 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 432 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 433 | "dev": true, 434 | "dependencies": { 435 | "once": "^1.3.0", 436 | "wrappy": "1" 437 | } 438 | }, 439 | "node_modules/inherits": { 440 | "version": "2.0.4", 441 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 442 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 443 | "dev": true 444 | }, 445 | "node_modules/is-binary-path": { 446 | "version": "2.1.0", 447 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 448 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 449 | "dev": true, 450 | "dependencies": { 451 | "binary-extensions": "^2.0.0" 452 | }, 453 | "engines": { 454 | "node": ">=8" 455 | } 456 | }, 457 | "node_modules/is-extglob": { 458 | "version": "2.1.1", 459 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 460 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 461 | "dev": true, 462 | "engines": { 463 | "node": ">=0.10.0" 464 | } 465 | }, 466 | "node_modules/is-fullwidth-code-point": { 467 | "version": "3.0.0", 468 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 469 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 470 | "dev": true, 471 | "engines": { 472 | "node": ">=8" 473 | } 474 | }, 475 | "node_modules/is-glob": { 476 | "version": "4.0.3", 477 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 478 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 479 | "dev": true, 480 | "dependencies": { 481 | "is-extglob": "^2.1.1" 482 | }, 483 | "engines": { 484 | "node": ">=0.10.0" 485 | } 486 | }, 487 | "node_modules/is-number": { 488 | "version": "7.0.0", 489 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 490 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 491 | "dev": true, 492 | "engines": { 493 | "node": ">=0.12.0" 494 | } 495 | }, 496 | "node_modules/is-plain-obj": { 497 | "version": "2.1.0", 498 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 499 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 500 | "dev": true, 501 | "engines": { 502 | "node": ">=8" 503 | } 504 | }, 505 | "node_modules/is-unicode-supported": { 506 | "version": "0.1.0", 507 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 508 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 509 | "dev": true, 510 | "engines": { 511 | "node": ">=10" 512 | }, 513 | "funding": { 514 | "url": "https://github.com/sponsors/sindresorhus" 515 | } 516 | }, 517 | "node_modules/js-yaml": { 518 | "version": "4.1.0", 519 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 520 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 521 | "dev": true, 522 | "dependencies": { 523 | "argparse": "^2.0.1" 524 | }, 525 | "bin": { 526 | "js-yaml": "bin/js-yaml.js" 527 | } 528 | }, 529 | "node_modules/locate-path": { 530 | "version": "6.0.0", 531 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 532 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 533 | "dev": true, 534 | "dependencies": { 535 | "p-locate": "^5.0.0" 536 | }, 537 | "engines": { 538 | "node": ">=10" 539 | }, 540 | "funding": { 541 | "url": "https://github.com/sponsors/sindresorhus" 542 | } 543 | }, 544 | "node_modules/lodash": { 545 | "version": "4.17.21", 546 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 547 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 548 | "dev": true 549 | }, 550 | "node_modules/log-symbols": { 551 | "version": "4.1.0", 552 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 553 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 554 | "dev": true, 555 | "dependencies": { 556 | "chalk": "^4.1.0", 557 | "is-unicode-supported": "^0.1.0" 558 | }, 559 | "engines": { 560 | "node": ">=10" 561 | }, 562 | "funding": { 563 | "url": "https://github.com/sponsors/sindresorhus" 564 | } 565 | }, 566 | "node_modules/minimatch": { 567 | "version": "5.1.6", 568 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 569 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 570 | "dev": true, 571 | "license": "ISC", 572 | "dependencies": { 573 | "brace-expansion": "^2.0.1" 574 | }, 575 | "engines": { 576 | "node": ">=10" 577 | } 578 | }, 579 | "node_modules/mocha": { 580 | "version": "10.8.2", 581 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", 582 | "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", 583 | "dev": true, 584 | "license": "MIT", 585 | "dependencies": { 586 | "ansi-colors": "^4.1.3", 587 | "browser-stdout": "^1.3.1", 588 | "chokidar": "^3.5.3", 589 | "debug": "^4.3.5", 590 | "diff": "^5.2.0", 591 | "escape-string-regexp": "^4.0.0", 592 | "find-up": "^5.0.0", 593 | "glob": "^8.1.0", 594 | "he": "^1.2.0", 595 | "js-yaml": "^4.1.0", 596 | "log-symbols": "^4.1.0", 597 | "minimatch": "^5.1.6", 598 | "ms": "^2.1.3", 599 | "serialize-javascript": "^6.0.2", 600 | "strip-json-comments": "^3.1.1", 601 | "supports-color": "^8.1.1", 602 | "workerpool": "^6.5.1", 603 | "yargs": "^16.2.0", 604 | "yargs-parser": "^20.2.9", 605 | "yargs-unparser": "^2.0.0" 606 | }, 607 | "bin": { 608 | "_mocha": "bin/_mocha", 609 | "mocha": "bin/mocha.js" 610 | }, 611 | "engines": { 612 | "node": ">= 14.0.0" 613 | } 614 | }, 615 | "node_modules/ms": { 616 | "version": "2.1.3", 617 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 618 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 619 | "dev": true 620 | }, 621 | "node_modules/nan": { 622 | "version": "2.22.2", 623 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", 624 | "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", 625 | "license": "MIT" 626 | }, 627 | "node_modules/normalize-path": { 628 | "version": "3.0.0", 629 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 630 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 631 | "dev": true, 632 | "engines": { 633 | "node": ">=0.10.0" 634 | } 635 | }, 636 | "node_modules/okay": { 637 | "version": "1.0.0", 638 | "resolved": "https://registry.npmjs.org/okay/-/okay-1.0.0.tgz", 639 | "integrity": "sha512-5/tIGJkNZgTgraoL2ovrkqSm5onNtpDLPoZfTOEjcaRxWOId5ZXLijvHltXsAaFRS0JMUokFLazo5DvNhVLxUQ==", 640 | "dev": true, 641 | "dependencies": { 642 | "sliced": "0.0.5" 643 | } 644 | }, 645 | "node_modules/once": { 646 | "version": "1.4.0", 647 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 648 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 649 | "dev": true, 650 | "dependencies": { 651 | "wrappy": "1" 652 | } 653 | }, 654 | "node_modules/p-limit": { 655 | "version": "3.1.0", 656 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 657 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 658 | "dev": true, 659 | "dependencies": { 660 | "yocto-queue": "^0.1.0" 661 | }, 662 | "engines": { 663 | "node": ">=10" 664 | }, 665 | "funding": { 666 | "url": "https://github.com/sponsors/sindresorhus" 667 | } 668 | }, 669 | "node_modules/p-locate": { 670 | "version": "5.0.0", 671 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 672 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 673 | "dev": true, 674 | "dependencies": { 675 | "p-limit": "^3.0.2" 676 | }, 677 | "engines": { 678 | "node": ">=10" 679 | }, 680 | "funding": { 681 | "url": "https://github.com/sponsors/sindresorhus" 682 | } 683 | }, 684 | "node_modules/path-exists": { 685 | "version": "4.0.0", 686 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 687 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 688 | "dev": true, 689 | "engines": { 690 | "node": ">=8" 691 | } 692 | }, 693 | "node_modules/picomatch": { 694 | "version": "2.3.1", 695 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 696 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 697 | "dev": true, 698 | "engines": { 699 | "node": ">=8.6" 700 | }, 701 | "funding": { 702 | "url": "https://github.com/sponsors/jonschlinkert" 703 | } 704 | }, 705 | "node_modules/randombytes": { 706 | "version": "2.1.0", 707 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 708 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 709 | "dev": true, 710 | "license": "MIT", 711 | "dependencies": { 712 | "safe-buffer": "^5.1.0" 713 | } 714 | }, 715 | "node_modules/readdirp": { 716 | "version": "3.6.0", 717 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 718 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 719 | "dev": true, 720 | "dependencies": { 721 | "picomatch": "^2.2.1" 722 | }, 723 | "engines": { 724 | "node": ">=8.10.0" 725 | } 726 | }, 727 | "node_modules/require-directory": { 728 | "version": "2.1.1", 729 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 730 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 731 | "dev": true, 732 | "engines": { 733 | "node": ">=0.10.0" 734 | } 735 | }, 736 | "node_modules/safe-buffer": { 737 | "version": "5.2.1", 738 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 739 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 740 | "dev": true, 741 | "funding": [ 742 | { 743 | "type": "github", 744 | "url": "https://github.com/sponsors/feross" 745 | }, 746 | { 747 | "type": "patreon", 748 | "url": "https://www.patreon.com/feross" 749 | }, 750 | { 751 | "type": "consulting", 752 | "url": "https://feross.org/support" 753 | } 754 | ], 755 | "license": "MIT" 756 | }, 757 | "node_modules/serialize-javascript": { 758 | "version": "6.0.2", 759 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", 760 | "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", 761 | "dev": true, 762 | "license": "BSD-3-Clause", 763 | "dependencies": { 764 | "randombytes": "^2.1.0" 765 | } 766 | }, 767 | "node_modules/sliced": { 768 | "version": "0.0.5", 769 | "resolved": "https://registry.npmjs.org/sliced/-/sliced-0.0.5.tgz", 770 | "integrity": "sha512-9bYT917D6H3+q8GlQBJmLVz3bc4OeVGfZ2BB12wvLnluTGfG6/8UdOUbKJDW1EEx9SZMDbjnatkau5/XcUeyOw==", 771 | "dev": true 772 | }, 773 | "node_modules/string-width": { 774 | "version": "4.2.3", 775 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 776 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 777 | "dev": true, 778 | "dependencies": { 779 | "emoji-regex": "^8.0.0", 780 | "is-fullwidth-code-point": "^3.0.0", 781 | "strip-ansi": "^6.0.1" 782 | }, 783 | "engines": { 784 | "node": ">=8" 785 | } 786 | }, 787 | "node_modules/strip-ansi": { 788 | "version": "6.0.1", 789 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 790 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 791 | "dev": true, 792 | "dependencies": { 793 | "ansi-regex": "^5.0.1" 794 | }, 795 | "engines": { 796 | "node": ">=8" 797 | } 798 | }, 799 | "node_modules/strip-json-comments": { 800 | "version": "3.1.1", 801 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 802 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 803 | "dev": true, 804 | "engines": { 805 | "node": ">=8" 806 | }, 807 | "funding": { 808 | "url": "https://github.com/sponsors/sindresorhus" 809 | } 810 | }, 811 | "node_modules/supports-color": { 812 | "version": "8.1.1", 813 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 814 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 815 | "dev": true, 816 | "dependencies": { 817 | "has-flag": "^4.0.0" 818 | }, 819 | "engines": { 820 | "node": ">=10" 821 | }, 822 | "funding": { 823 | "url": "https://github.com/chalk/supports-color?sponsor=1" 824 | } 825 | }, 826 | "node_modules/to-regex-range": { 827 | "version": "5.0.1", 828 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 829 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 830 | "dev": true, 831 | "dependencies": { 832 | "is-number": "^7.0.0" 833 | }, 834 | "engines": { 835 | "node": ">=8.0" 836 | } 837 | }, 838 | "node_modules/workerpool": { 839 | "version": "6.5.1", 840 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", 841 | "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", 842 | "dev": true, 843 | "license": "Apache-2.0" 844 | }, 845 | "node_modules/wrap-ansi": { 846 | "version": "7.0.0", 847 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 848 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 849 | "dev": true, 850 | "dependencies": { 851 | "ansi-styles": "^4.0.0", 852 | "string-width": "^4.1.0", 853 | "strip-ansi": "^6.0.0" 854 | }, 855 | "engines": { 856 | "node": ">=10" 857 | }, 858 | "funding": { 859 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 860 | } 861 | }, 862 | "node_modules/wrappy": { 863 | "version": "1.0.2", 864 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 865 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 866 | "dev": true 867 | }, 868 | "node_modules/y18n": { 869 | "version": "5.0.8", 870 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 871 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 872 | "dev": true, 873 | "engines": { 874 | "node": ">=10" 875 | } 876 | }, 877 | "node_modules/yargs": { 878 | "version": "16.2.0", 879 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 880 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 881 | "dev": true, 882 | "dependencies": { 883 | "cliui": "^7.0.2", 884 | "escalade": "^3.1.1", 885 | "get-caller-file": "^2.0.5", 886 | "require-directory": "^2.1.1", 887 | "string-width": "^4.2.0", 888 | "y18n": "^5.0.5", 889 | "yargs-parser": "^20.2.2" 890 | }, 891 | "engines": { 892 | "node": ">=10" 893 | } 894 | }, 895 | "node_modules/yargs-parser": { 896 | "version": "20.2.9", 897 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 898 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 899 | "dev": true, 900 | "license": "ISC", 901 | "engines": { 902 | "node": ">=10" 903 | } 904 | }, 905 | "node_modules/yargs-unparser": { 906 | "version": "2.0.0", 907 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 908 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 909 | "dev": true, 910 | "dependencies": { 911 | "camelcase": "^6.0.0", 912 | "decamelize": "^4.0.0", 913 | "flat": "^5.0.2", 914 | "is-plain-obj": "^2.1.0" 915 | }, 916 | "engines": { 917 | "node": ">=10" 918 | } 919 | }, 920 | "node_modules/yocto-queue": { 921 | "version": "0.1.0", 922 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 923 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 924 | "dev": true, 925 | "engines": { 926 | "node": ">=10" 927 | }, 928 | "funding": { 929 | "url": "https://github.com/sponsors/sindresorhus" 930 | } 931 | } 932 | } 933 | } 934 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libpq", 3 | "version": "1.8.14", 4 | "description": "Low-level native bindings to PostgreSQL libpq", 5 | "main": "index.js", 6 | "keywords": [ 7 | "postgres", 8 | "libpq" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/brianc/node-libpq.git" 13 | }, 14 | "scripts": { 15 | "clean": "node-gyp clean", 16 | "pretest": "node-gyp rebuild", 17 | "test": "node_modules/.bin/mocha" 18 | }, 19 | "author": "Brian M. Carlson", 20 | "license": "MIT", 21 | "dependencies": { 22 | "bindings": "1.5.0", 23 | "nan": "~2.22.2" 24 | }, 25 | "devDependencies": { 26 | "async": "^2.6.2", 27 | "buffer-from": "^1.1.1", 28 | "lodash": "^4.17.11", 29 | "mocha": "^10.8.2", 30 | "okay": "^1.0.0" 31 | }, 32 | "prettier": { 33 | "singleQuote": true 34 | } 35 | } -------------------------------------------------------------------------------- /src/addon.cc: -------------------------------------------------------------------------------- 1 | #include "addon.h" 2 | 3 | // Initialize the node addon 4 | NAN_MODULE_INIT(InitAddon) { 5 | 6 | v8::Local tpl = Nan::New(Connection::Create); 7 | tpl->SetClassName(Nan::New("PQ").ToLocalChecked()); 8 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 9 | 10 | //connection initialization & management functions 11 | Nan::SetPrototypeMethod(tpl, "$connectSync", Connection::ConnectSync); 12 | Nan::SetPrototypeMethod(tpl, "$connect", Connection::Connect); 13 | Nan::SetPrototypeMethod(tpl, "$finish", Connection::Finish); 14 | Nan::SetPrototypeMethod(tpl, "$getLastErrorMessage", Connection::GetLastErrorMessage); 15 | Nan::SetPrototypeMethod(tpl, "$resultErrorFields", Connection::ResultErrorFields); 16 | Nan::SetPrototypeMethod(tpl, "$socket", Connection::Socket); 17 | Nan::SetPrototypeMethod(tpl, "$serverVersion", Connection::ServerVersion); 18 | 19 | //sync query functions 20 | Nan::SetPrototypeMethod(tpl, "$exec", Connection::Exec); 21 | Nan::SetPrototypeMethod(tpl, "$execParams", Connection::ExecParams); 22 | Nan::SetPrototypeMethod(tpl, "$prepare", Connection::Prepare); 23 | Nan::SetPrototypeMethod(tpl, "$execPrepared", Connection::ExecPrepared); 24 | Nan::SetPrototypeMethod(tpl, "$describePrepared", Connection::DescribePrepared); 25 | 26 | //async query functions 27 | Nan::SetPrototypeMethod(tpl, "$sendQuery", Connection::SendQuery); 28 | Nan::SetPrototypeMethod(tpl, "$sendQueryParams", Connection::SendQueryParams); 29 | Nan::SetPrototypeMethod(tpl, "$sendPrepare", Connection::SendPrepare); 30 | Nan::SetPrototypeMethod(tpl, "$sendQueryPrepared", Connection::SendQueryPrepared); 31 | Nan::SetPrototypeMethod(tpl, "$getResult", Connection::GetResult); 32 | 33 | //async i/o control functions 34 | Nan::SetPrototypeMethod(tpl, "$startRead", Connection::StartRead); 35 | Nan::SetPrototypeMethod(tpl, "$stopRead", Connection::StopRead); 36 | Nan::SetPrototypeMethod(tpl, "$startWrite", Connection::StartWrite); 37 | Nan::SetPrototypeMethod(tpl, "$consumeInput", Connection::ConsumeInput); 38 | Nan::SetPrototypeMethod(tpl, "$isBusy", Connection::IsBusy); 39 | Nan::SetPrototypeMethod(tpl, "$setNonBlocking", Connection::SetNonBlocking); 40 | Nan::SetPrototypeMethod(tpl, "$isNonBlocking", Connection::IsNonBlocking); 41 | Nan::SetPrototypeMethod(tpl, "$flush", Connection::Flush); 42 | 43 | //result accessor functions 44 | Nan::SetPrototypeMethod(tpl, "$clear", Connection::Clear); 45 | Nan::SetPrototypeMethod(tpl, "$ntuples", Connection::Ntuples); 46 | Nan::SetPrototypeMethod(tpl, "$nfields", Connection::Nfields); 47 | Nan::SetPrototypeMethod(tpl, "$fname", Connection::Fname); 48 | Nan::SetPrototypeMethod(tpl, "$ftable", Connection::Ftable); 49 | Nan::SetPrototypeMethod(tpl, "$ftablecol", Connection::Ftablecol); 50 | Nan::SetPrototypeMethod(tpl, "$ftype", Connection::Ftype); 51 | Nan::SetPrototypeMethod(tpl, "$getvalue", Connection::Getvalue); 52 | Nan::SetPrototypeMethod(tpl, "$getisnull", Connection::Getisnull); 53 | Nan::SetPrototypeMethod(tpl, "$nparams", Connection::Nparams); 54 | Nan::SetPrototypeMethod(tpl, "$paramtype", Connection::Paramtype); 55 | Nan::SetPrototypeMethod(tpl, "$cmdStatus", Connection::CmdStatus); 56 | Nan::SetPrototypeMethod(tpl, "$cmdTuples", Connection::CmdTuples); 57 | Nan::SetPrototypeMethod(tpl, "$resultStatus", Connection::ResultStatus); 58 | Nan::SetPrototypeMethod(tpl, "$resultErrorMessage", Connection::ResultErrorMessage); 59 | 60 | //string escaping functions 61 | #ifdef ESCAPE_SUPPORTED 62 | Nan::SetPrototypeMethod(tpl, "$escapeLiteral", Connection::EscapeLiteral); 63 | Nan::SetPrototypeMethod(tpl, "$escapeIdentifier", Connection::EscapeIdentifier); 64 | #endif 65 | 66 | //async notifications 67 | Nan::SetPrototypeMethod(tpl, "$notifies", Connection::Notifies); 68 | 69 | //COPY IN/OUT 70 | Nan::SetPrototypeMethod(tpl, "$putCopyData", Connection::PutCopyData); 71 | Nan::SetPrototypeMethod(tpl, "$putCopyEnd", Connection::PutCopyEnd); 72 | Nan::SetPrototypeMethod(tpl, "$getCopyData", Connection::GetCopyData); 73 | 74 | //Cancel 75 | Nan::SetPrototypeMethod(tpl, "$cancel", Connection::Cancel); 76 | 77 | Nan::Set(target, 78 | Nan::New("PQ").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); 79 | } 80 | 81 | NODE_MODULE(addon, InitAddon) 82 | -------------------------------------------------------------------------------- /src/addon.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_LIBPQ_ADDON 2 | #define NODE_LIBPQ_ADDON 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #if PG_VERSION_NUM > 90000 9 | #define ESCAPE_SUPPORTED 10 | #endif 11 | 12 | #if PG_VERSION_NUM >= 93000 13 | #define MORE_ERROR_FIELDS_SUPPORTED 14 | #endif 15 | 16 | #include "connection.h" 17 | #include "connect-async-worker.h" 18 | 19 | //#define LOG(msg) fprintf(stderr, "%s\n", msg); 20 | //#define TRACEF(format, arg) fprintf(stderr, format, arg); 21 | 22 | #define LOG(msg) ; 23 | #define TRACEF(format, arg) ; 24 | 25 | #define TRACE(msg) LOG(msg); 26 | #define NODE_THIS() Nan::ObjectWrap::Unwrap(info.This()); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/connect-async-worker.cc: -------------------------------------------------------------------------------- 1 | //helper class to perform async connection 2 | #include "addon.h" 3 | 4 | ConnectAsyncWorker::ConnectAsyncWorker(v8::Local paramString, Connection* conn, Nan::Callback* callback) 5 | : Nan::AsyncWorker(callback), conn(conn), paramString(paramString) { } 6 | 7 | ConnectAsyncWorker::~ConnectAsyncWorker() { } 8 | 9 | //this method fires within the threadpool and does not 10 | //block the main node run loop 11 | void ConnectAsyncWorker::Execute() { 12 | TRACE("ConnectAsyncWorker::Execute"); 13 | 14 | bool success = conn->ConnectDB(*paramString); 15 | 16 | if(!success) { 17 | SetErrorMessage(conn->ErrorMessage()); 18 | } 19 | } 20 | 21 | void ConnectAsyncWorker::HandleOKCallback() { 22 | Nan::HandleScope scope; 23 | 24 | conn->InitPollSocket(); 25 | callback->Call(0, NULL, async_resource); 26 | } 27 | -------------------------------------------------------------------------------- /src/connect-async-worker.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_LIBPQ_CONNECT_ASYNC_WORKER 2 | #define NODE_LIBPQ_CONNECT_ASYNC_WORKER 3 | 4 | #include "addon.h" 5 | 6 | class ConnectAsyncWorker : public Nan::AsyncWorker { 7 | public: 8 | ConnectAsyncWorker(v8::Local paramString, Connection* conn, Nan::Callback* callback); 9 | ~ConnectAsyncWorker(); 10 | void Execute(); 11 | void HandleOKCallback(); 12 | 13 | private: 14 | Connection* conn; 15 | Nan::Utf8String paramString; 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/connection.cc: -------------------------------------------------------------------------------- 1 | #include "addon.h" 2 | 3 | Connection::Connection() : Nan::ObjectWrap() { 4 | TRACE("Connection::Constructor"); 5 | pq = NULL; 6 | lastResult = NULL; 7 | is_reading = false; 8 | is_reffed = false; 9 | is_success_poll_init = false; 10 | poll_watcher.data = this; 11 | } 12 | 13 | NAN_METHOD(Connection::Create) { 14 | TRACE("Building new instance"); 15 | Connection* conn = new Connection(); 16 | conn->Wrap(info.This()); 17 | 18 | info.GetReturnValue().Set(info.This()); 19 | } 20 | 21 | NAN_METHOD(Connection::ConnectSync) { 22 | TRACE("Connection::ConnectSync::begin"); 23 | 24 | Connection *self = Nan::ObjectWrap::Unwrap(info.This()); 25 | 26 | self->Ref(); 27 | self->is_reffed = true; 28 | bool success = self->ConnectDB(*Nan::Utf8String(info[0])); 29 | if (success) { 30 | self->InitPollSocket(); 31 | } 32 | info.GetReturnValue().Set(success); 33 | } 34 | 35 | NAN_METHOD(Connection::Connect) { 36 | TRACE("Connection::Connect"); 37 | 38 | Connection* self = NODE_THIS(); 39 | 40 | v8::Local callback = info[1].As(); 41 | LOG("About to make callback"); 42 | Nan::Callback* nanCallback = new Nan::Callback(callback); 43 | LOG("About to instantiate worker"); 44 | ConnectAsyncWorker* worker = new ConnectAsyncWorker(info[0].As(), self, nanCallback); 45 | LOG("Instantiated worker, running it..."); 46 | self->Ref(); 47 | self->is_reffed = true; 48 | worker->SaveToPersistent(Nan::New("PQConnectAsyncWorker").ToLocalChecked(), info.This()); 49 | Nan::AsyncQueueWorker(worker); 50 | } 51 | 52 | NAN_METHOD(Connection::Socket) { 53 | TRACE("Connection::Socket"); 54 | 55 | Connection *self = NODE_THIS(); 56 | int fd = PQsocket(self->pq); 57 | TRACEF("Connection::Socket::fd: %d\n", fd); 58 | 59 | info.GetReturnValue().Set(fd); 60 | } 61 | 62 | NAN_METHOD(Connection::GetLastErrorMessage) { 63 | Connection *self = NODE_THIS(); 64 | char* errorMessage = PQerrorMessage(self->pq); 65 | 66 | info.GetReturnValue().Set(Nan::New(errorMessage).ToLocalChecked()); 67 | } 68 | 69 | NAN_METHOD(Connection::Finish) { 70 | TRACE("Connection::Finish::finish"); 71 | 72 | Connection *self = NODE_THIS(); 73 | 74 | self->ReadStop(); 75 | if (self->is_success_poll_init) { 76 | uv_close((uv_handle_t*) &self->poll_watcher, NULL); 77 | self->is_success_poll_init = false; 78 | } 79 | self->ClearLastResult(); 80 | PQfinish(self->pq); 81 | self->pq = NULL; 82 | if(self->is_reffed) { 83 | self->is_reffed = false; 84 | self->Unref(); 85 | } 86 | } 87 | 88 | NAN_METHOD(Connection::ServerVersion) { 89 | TRACE("Connection::ServerVersion"); 90 | Connection* self = NODE_THIS(); 91 | info.GetReturnValue().Set(PQserverVersion(self->pq)); 92 | } 93 | 94 | 95 | NAN_METHOD(Connection::Exec) { 96 | Connection *self = NODE_THIS(); 97 | Nan::Utf8String commandText(info[0]); 98 | 99 | TRACEF("Connection::Exec: %s\n", *commandText); 100 | PGresult* result = PQexec(self->pq, *commandText); 101 | 102 | self->SetLastResult(result); 103 | } 104 | 105 | NAN_METHOD(Connection::ExecParams) { 106 | Connection *self = NODE_THIS(); 107 | 108 | Nan::Utf8String commandText(info[0]); 109 | TRACEF("Connection::Exec: %s\n", *commandText); 110 | 111 | v8::Local jsParams = v8::Local::Cast(info[1]); 112 | 113 | int numberOfParams = jsParams->Length(); 114 | char **parameters = NewCStringArray(jsParams); 115 | 116 | PGresult* result = PQexecParams( 117 | self->pq, 118 | *commandText, 119 | numberOfParams, 120 | NULL, //const Oid* paramTypes[], 121 | parameters, //const char* const* paramValues[] 122 | NULL, //const int* paramLengths[] 123 | NULL, //const int* paramFormats[], 124 | 0 //result format of text 125 | ); 126 | 127 | DeleteCStringArray(parameters, numberOfParams); 128 | 129 | self->SetLastResult(result); 130 | } 131 | 132 | NAN_METHOD(Connection::Prepare) { 133 | Connection *self = NODE_THIS(); 134 | 135 | Nan::Utf8String statementName(info[0]); 136 | Nan::Utf8String commandText(info[1]); 137 | int numberOfParams = Nan::To(info[2]).FromJust(); 138 | 139 | TRACEF("Connection::Prepare: %s\n", *statementName); 140 | 141 | PGresult* result = PQprepare( 142 | self->pq, 143 | *statementName, 144 | *commandText, 145 | numberOfParams, 146 | NULL //const Oid* paramTypes[] 147 | ); 148 | 149 | self->SetLastResult(result); 150 | } 151 | 152 | NAN_METHOD(Connection::ExecPrepared) { 153 | Connection *self = NODE_THIS(); 154 | 155 | Nan::Utf8String statementName(info[0]); 156 | 157 | TRACEF("Connection::ExecPrepared: %s\n", *statementName); 158 | 159 | v8::Local jsParams = v8::Local::Cast(info[1]); 160 | 161 | int numberOfParams = jsParams->Length(); 162 | char** parameters = NewCStringArray(jsParams); 163 | 164 | PGresult* result = PQexecPrepared( 165 | self->pq, 166 | *statementName, 167 | numberOfParams, 168 | parameters, //const char* const* paramValues[] 169 | NULL, //const int* paramLengths[] 170 | NULL, //const int* paramFormats[], 171 | 0 //result format of text 172 | ); 173 | 174 | DeleteCStringArray(parameters, numberOfParams); 175 | 176 | self->SetLastResult(result); 177 | } 178 | 179 | NAN_METHOD(Connection::DescribePrepared) { 180 | Connection *self = NODE_THIS(); 181 | 182 | Nan::Utf8String statementName(info[0]); 183 | 184 | TRACEF("Connection::DescribePrepared: %s\n", *statementName); 185 | 186 | PGresult* result = PQdescribePrepared( 187 | self->pq, 188 | *statementName 189 | ); 190 | 191 | self->SetLastResult(result); 192 | } 193 | 194 | 195 | NAN_METHOD(Connection::Clear) { 196 | TRACE("Connection::Clear"); 197 | Connection *self = NODE_THIS(); 198 | 199 | self->ClearLastResult(); 200 | } 201 | 202 | NAN_METHOD(Connection::Ntuples) { 203 | TRACE("Connection::Ntuples"); 204 | Connection *self = NODE_THIS(); 205 | PGresult* res = self->lastResult; 206 | int numTuples = PQntuples(res); 207 | 208 | info.GetReturnValue().Set(numTuples); 209 | } 210 | 211 | NAN_METHOD(Connection::Nfields) { 212 | TRACE("Connection::Nfields"); 213 | Connection *self = NODE_THIS(); 214 | PGresult* res = self->lastResult; 215 | int numFields = PQnfields(res); 216 | 217 | info.GetReturnValue().Set(numFields); 218 | } 219 | 220 | NAN_METHOD(Connection::Fname) { 221 | TRACE("Connection::Fname"); 222 | Connection *self = NODE_THIS(); 223 | 224 | PGresult* res = self->lastResult; 225 | 226 | char* colName = PQfname(res, Nan::To(info[0]).FromJust()); 227 | 228 | if(colName == NULL) { 229 | return info.GetReturnValue().SetNull(); 230 | } 231 | 232 | info.GetReturnValue().Set(Nan::New(colName).ToLocalChecked()); 233 | } 234 | 235 | NAN_METHOD(Connection::Ftable) { 236 | TRACE("Connection::Ftable"); 237 | Connection *self = NODE_THIS(); 238 | 239 | PGresult* res = self->lastResult; 240 | 241 | int table = PQftable(res, Nan::To(info[0]).FromJust()); 242 | 243 | info.GetReturnValue().Set(table); 244 | } 245 | 246 | NAN_METHOD(Connection::Ftablecol) { 247 | TRACE("Connection::Ftablecol"); 248 | Connection *self = NODE_THIS(); 249 | 250 | PGresult* res = self->lastResult; 251 | 252 | int tablecol = PQftablecol(res, Nan::To(info[0]).FromJust()); 253 | 254 | info.GetReturnValue().Set(tablecol); 255 | } 256 | 257 | NAN_METHOD(Connection::Ftype) { 258 | TRACE("Connection::Ftype"); 259 | Connection *self = NODE_THIS(); 260 | 261 | PGresult* res = self->lastResult; 262 | 263 | int colName = PQftype(res, Nan::To(info[0]).FromJust()); 264 | 265 | info.GetReturnValue().Set(colName); 266 | } 267 | 268 | NAN_METHOD(Connection::Getvalue) { 269 | TRACE("Connection::Getvalue"); 270 | Connection *self = NODE_THIS(); 271 | 272 | PGresult* res = self->lastResult; 273 | 274 | int rowNumber = Nan::To(info[0]).FromJust(); 275 | int colNumber = Nan::To(info[1]).FromJust(); 276 | 277 | char* rowValue = PQgetvalue(res, rowNumber, colNumber); 278 | 279 | if(rowValue == NULL) { 280 | return info.GetReturnValue().SetNull(); 281 | } 282 | 283 | info.GetReturnValue().Set(Nan::New(rowValue).ToLocalChecked()); 284 | } 285 | 286 | NAN_METHOD(Connection::Getisnull) { 287 | TRACE("Connection::Getisnull"); 288 | Connection *self = NODE_THIS(); 289 | 290 | PGresult* res = self->lastResult; 291 | 292 | int rowNumber = Nan::To(info[0]).FromJust(); 293 | int colNumber = Nan::To(info[1]).FromJust(); 294 | 295 | int rowValue = PQgetisnull(res, rowNumber, colNumber); 296 | 297 | info.GetReturnValue().Set(rowValue == 1); 298 | } 299 | 300 | NAN_METHOD(Connection::Nparams) { 301 | TRACE("Connection::Nparams"); 302 | Connection *self = NODE_THIS(); 303 | 304 | PGresult* res = self->lastResult; 305 | 306 | int nparams = PQnparams(res); 307 | 308 | info.GetReturnValue().Set(nparams); 309 | } 310 | 311 | NAN_METHOD(Connection::Paramtype) { 312 | TRACE("Connection::Paramtype"); 313 | Connection *self = NODE_THIS(); 314 | 315 | PGresult* res = self->lastResult; 316 | 317 | int paramType = PQparamtype(res, Nan::To(info[0]).FromJust()); 318 | 319 | info.GetReturnValue().Set(paramType); 320 | } 321 | 322 | NAN_METHOD(Connection::CmdStatus) { 323 | TRACE("Connection::CmdStatus"); 324 | Connection *self = NODE_THIS(); 325 | 326 | PGresult* res = self->lastResult; 327 | char* status = PQcmdStatus(res); 328 | 329 | info.GetReturnValue().Set(Nan::New(status).ToLocalChecked()); 330 | } 331 | 332 | NAN_METHOD(Connection::CmdTuples) { 333 | TRACE("Connection::CmdTuples"); 334 | Connection *self = NODE_THIS(); 335 | 336 | PGresult* res = self->lastResult; 337 | char* tuples = PQcmdTuples(res); 338 | 339 | info.GetReturnValue().Set(Nan::New(tuples).ToLocalChecked()); 340 | } 341 | 342 | NAN_METHOD(Connection::ResultStatus) { 343 | TRACE("Connection::ResultStatus"); 344 | Connection *self = NODE_THIS(); 345 | 346 | PGresult* res = self->lastResult; 347 | 348 | char* status = PQresStatus(PQresultStatus(res)); 349 | 350 | info.GetReturnValue().Set(Nan::New(status).ToLocalChecked()); 351 | } 352 | 353 | NAN_METHOD(Connection::ResultErrorMessage) { 354 | TRACE("Connection::ResultErrorMessage"); 355 | Connection *self = NODE_THIS(); 356 | 357 | PGresult* res = self->lastResult; 358 | 359 | char* status = PQresultErrorMessage(res); 360 | 361 | info.GetReturnValue().Set(Nan::New(status).ToLocalChecked()); 362 | } 363 | 364 | # define SET_E(key, name) \ 365 | field = PQresultErrorField(self->lastResult, key); \ 366 | if(field != NULL) { \ 367 | Nan::Set(result, \ 368 | Nan::New(name).ToLocalChecked(), Nan::New(field).ToLocalChecked()); \ 369 | } 370 | 371 | NAN_METHOD(Connection::ResultErrorFields) { 372 | Connection *self = NODE_THIS(); 373 | 374 | if(self->lastResult == NULL) { 375 | return info.GetReturnValue().SetNull(); 376 | } 377 | 378 | v8::Local result = Nan::New(); 379 | char* field; 380 | SET_E(PG_DIAG_SEVERITY, "severity"); 381 | SET_E(PG_DIAG_SQLSTATE, "sqlState"); 382 | SET_E(PG_DIAG_MESSAGE_PRIMARY, "messagePrimary"); 383 | SET_E(PG_DIAG_MESSAGE_DETAIL, "messageDetail"); 384 | SET_E(PG_DIAG_MESSAGE_HINT, "messageHint"); 385 | SET_E(PG_DIAG_STATEMENT_POSITION, "statementPosition"); 386 | SET_E(PG_DIAG_INTERNAL_POSITION, "internalPosition"); 387 | SET_E(PG_DIAG_INTERNAL_QUERY, "internalQuery"); 388 | SET_E(PG_DIAG_CONTEXT, "context"); 389 | #ifdef MORE_ERROR_FIELDS_SUPPORTED 390 | SET_E(PG_DIAG_SCHEMA_NAME, "schemaName"); 391 | SET_E(PG_DIAG_TABLE_NAME, "tableName"); 392 | SET_E(PG_DIAG_COLUMN_NAME, "columnName"); 393 | SET_E(PG_DIAG_DATATYPE_NAME, "dataTypeName"); 394 | SET_E(PG_DIAG_CONSTRAINT_NAME, "constraintName"); 395 | #endif 396 | SET_E(PG_DIAG_SOURCE_FILE, "sourceFile"); 397 | SET_E(PG_DIAG_SOURCE_LINE, "sourceLine"); 398 | SET_E(PG_DIAG_SOURCE_FUNCTION, "sourceFunction"); 399 | info.GetReturnValue().Set(result); 400 | } 401 | 402 | NAN_METHOD(Connection::SendQuery) { 403 | TRACE("Connection::SendQuery"); 404 | 405 | Connection *self = NODE_THIS(); 406 | Nan::Utf8String commandText(info[0]); 407 | 408 | TRACEF("Connection::SendQuery: %s\n", *commandText); 409 | int success = PQsendQuery(self->pq, *commandText); 410 | 411 | info.GetReturnValue().Set(success == 1); 412 | } 413 | 414 | NAN_METHOD(Connection::SendQueryParams) { 415 | TRACE("Connection::SendQueryParams"); 416 | 417 | Connection *self = NODE_THIS(); 418 | 419 | Nan::Utf8String commandText(info[0]); 420 | TRACEF("Connection::SendQueryParams: %s\n", *commandText); 421 | 422 | v8::Local jsParams = v8::Local::Cast(info[1]); 423 | 424 | int numberOfParams = jsParams->Length(); 425 | char** parameters = NewCStringArray(jsParams); 426 | 427 | int success = PQsendQueryParams( 428 | self->pq, 429 | *commandText, 430 | numberOfParams, 431 | NULL, //const Oid* paramTypes[], 432 | parameters, //const char* const* paramValues[] 433 | NULL, //const int* paramLengths[] 434 | NULL, //const int* paramFormats[], 435 | 0 //result format of text 436 | ); 437 | 438 | DeleteCStringArray(parameters, numberOfParams); 439 | 440 | info.GetReturnValue().Set(success == 1); 441 | } 442 | 443 | NAN_METHOD(Connection::SendPrepare) { 444 | TRACE("Connection::SendPrepare"); 445 | 446 | Connection *self = NODE_THIS(); 447 | 448 | Nan::Utf8String statementName(info[0]); 449 | Nan::Utf8String commandText(info[1]); 450 | int numberOfParams = Nan::To(info[2]).FromJust(); 451 | 452 | TRACEF("Connection::SendPrepare: %s\n", *statementName); 453 | int success = PQsendPrepare( 454 | self->pq, 455 | *statementName, 456 | *commandText, 457 | numberOfParams, 458 | NULL //const Oid* paramTypes 459 | ); 460 | 461 | info.GetReturnValue().Set(success == 1); 462 | } 463 | 464 | NAN_METHOD(Connection::SendQueryPrepared) { 465 | TRACE("Connection::SendQueryPrepared"); 466 | 467 | Connection *self = NODE_THIS(); 468 | 469 | Nan::Utf8String statementName(info[0]); 470 | TRACEF("Connection::SendQueryPrepared: %s\n", *statementName); 471 | 472 | v8::Local jsParams = v8::Local::Cast(info[1]); 473 | 474 | int numberOfParams = jsParams->Length(); 475 | char** parameters = NewCStringArray(jsParams); 476 | 477 | int success = PQsendQueryPrepared( 478 | self->pq, 479 | *statementName, 480 | numberOfParams, 481 | parameters, //const char* const* paramValues[] 482 | NULL, //const int* paramLengths[] 483 | NULL, //const int* paramFormats[], 484 | 0 //result format of text 485 | ); 486 | 487 | DeleteCStringArray(parameters, numberOfParams); 488 | 489 | info.GetReturnValue().Set(success == 1); 490 | } 491 | 492 | NAN_METHOD(Connection::GetResult) { 493 | TRACE("Connection::GetResult"); 494 | 495 | Connection *self = NODE_THIS(); 496 | PGresult *result = PQgetResult(self->pq); 497 | 498 | if(result == NULL) { 499 | return info.GetReturnValue().Set(false); 500 | } 501 | 502 | self->SetLastResult(result); 503 | info.GetReturnValue().Set(true); 504 | } 505 | 506 | NAN_METHOD(Connection::ConsumeInput) { 507 | TRACE("Connection::ConsumeInput"); 508 | 509 | Connection *self = NODE_THIS(); 510 | 511 | int success = PQconsumeInput(self->pq); 512 | info.GetReturnValue().Set(success == 1); 513 | } 514 | 515 | NAN_METHOD(Connection::IsBusy) { 516 | TRACE("Connection::IsBusy"); 517 | 518 | Connection *self = NODE_THIS(); 519 | 520 | int isBusy = PQisBusy(self->pq); 521 | TRACEF("Connection::IsBusy: %d\n", isBusy); 522 | 523 | info.GetReturnValue().Set(isBusy == 1); 524 | } 525 | 526 | NAN_METHOD(Connection::StartRead) { 527 | TRACE("Connection::StartRead"); 528 | 529 | Connection* self = NODE_THIS(); 530 | 531 | self->ReadStart(); 532 | } 533 | 534 | NAN_METHOD(Connection::StopRead) { 535 | TRACE("Connection::StopRead"); 536 | 537 | Connection* self = NODE_THIS(); 538 | 539 | self->ReadStop(); 540 | } 541 | 542 | NAN_METHOD(Connection::StartWrite) { 543 | TRACE("Connection::StartWrite"); 544 | 545 | Connection* self = NODE_THIS(); 546 | 547 | self->WriteStart(); 548 | } 549 | 550 | NAN_METHOD(Connection::SetNonBlocking) { 551 | TRACE("Connection::SetNonBlocking"); 552 | 553 | Connection* self = NODE_THIS(); 554 | 555 | int ok = PQsetnonblocking(self->pq, Nan::To(info[0]).FromJust()); 556 | 557 | info.GetReturnValue().Set(ok == 0); 558 | } 559 | 560 | NAN_METHOD(Connection::IsNonBlocking) { 561 | TRACE("Connection::IsNonBlocking"); 562 | 563 | Connection* self = NODE_THIS(); 564 | 565 | int status = PQisnonblocking(self->pq); 566 | 567 | info.GetReturnValue().Set(status == 1); 568 | } 569 | 570 | NAN_METHOD(Connection::Flush) { 571 | TRACE("Connection::Flush"); 572 | 573 | Connection* self = NODE_THIS(); 574 | 575 | int status = PQflush(self->pq); 576 | 577 | info.GetReturnValue().Set(status); 578 | } 579 | 580 | #ifdef ESCAPE_SUPPORTED 581 | NAN_METHOD(Connection::EscapeLiteral) { 582 | TRACE("Connection::EscapeLiteral"); 583 | 584 | Connection* self = NODE_THIS(); 585 | 586 | Nan::Utf8String str(Nan::To(info[0]).ToLocalChecked()); 587 | 588 | TRACEF("Connection::EscapeLiteral:input %s\n", *str); 589 | char* result = PQescapeLiteral(self->pq, *str, str.length()); 590 | TRACEF("Connection::EscapeLiteral:output %s\n", result); 591 | 592 | if(result == NULL) { 593 | return info.GetReturnValue().SetNull(); 594 | } 595 | 596 | info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); 597 | PQfreemem(result); 598 | } 599 | 600 | NAN_METHOD(Connection::EscapeIdentifier) { 601 | TRACE("Connection::EscapeIdentifier"); 602 | 603 | Connection* self = NODE_THIS(); 604 | 605 | Nan::Utf8String str(Nan::To(info[0]).ToLocalChecked()); 606 | 607 | TRACEF("Connection::EscapeIdentifier:input %s\n", *str); 608 | char* result = PQescapeIdentifier(self->pq, *str, str.length()); 609 | TRACEF("Connection::EscapeIdentifier:output %s\n", result); 610 | 611 | if(result == NULL) { 612 | return info.GetReturnValue().SetNull(); 613 | } 614 | 615 | info.GetReturnValue().Set(Nan::New(result).ToLocalChecked()); 616 | PQfreemem(result); 617 | } 618 | #endif 619 | 620 | NAN_METHOD(Connection::Notifies) { 621 | LOG("Connection::Notifies"); 622 | 623 | Connection* self = NODE_THIS(); 624 | 625 | PGnotify* msg = PQnotifies(self->pq); 626 | 627 | if(msg == NULL) { 628 | LOG("No notification"); 629 | return; 630 | } 631 | 632 | v8::Local result = Nan::New(); 633 | Nan::Set(result, Nan::New("relname").ToLocalChecked(), Nan::New(msg->relname).ToLocalChecked()); 634 | Nan::Set(result, Nan::New("extra").ToLocalChecked(), Nan::New(msg->extra).ToLocalChecked()); 635 | Nan::Set(result, Nan::New("be_pid").ToLocalChecked(), Nan::New(msg->be_pid)); 636 | 637 | PQfreemem(msg); 638 | 639 | info.GetReturnValue().Set(result); 640 | }; 641 | 642 | NAN_METHOD(Connection::PutCopyData) { 643 | LOG("Connection::PutCopyData"); 644 | 645 | Connection* self = NODE_THIS(); 646 | 647 | v8::Local buffer = info[0].As(); 648 | 649 | char* data = node::Buffer::Data(buffer); 650 | int length = node::Buffer::Length(buffer); 651 | 652 | int result = PQputCopyData(self->pq, data, length); 653 | 654 | info.GetReturnValue().Set(result); 655 | } 656 | 657 | NAN_METHOD(Connection::PutCopyEnd) { 658 | LOG("Connection::PutCopyEnd"); 659 | 660 | Connection* self = NODE_THIS(); 661 | 662 | //optional error message 663 | 664 | bool sendErrorMessage = info.Length() > 0; 665 | int result; 666 | if(sendErrorMessage) { 667 | Nan::Utf8String msg(info[0]); 668 | TRACEF("Connection::PutCopyEnd:%s\n", *msg); 669 | result = PQputCopyEnd(self->pq, *msg); 670 | } else { 671 | result = PQputCopyEnd(self->pq, NULL); 672 | } 673 | 674 | info.GetReturnValue().Set(result); 675 | } 676 | 677 | static void FreeBuffer(char *buffer, void *) { 678 | PQfreemem(buffer); 679 | } 680 | 681 | NAN_METHOD(Connection::GetCopyData) { 682 | LOG("Connection::GetCopyData"); 683 | 684 | Connection* self = NODE_THIS(); 685 | 686 | char* buffer = NULL; 687 | int async = info[0]->IsTrue() ? 1 : 0; 688 | 689 | TRACEF("Connection::GetCopyData:async %d\n", async); 690 | 691 | int length = PQgetCopyData(self->pq, &buffer, async); 692 | 693 | //some sort of failure or not-ready condition 694 | if(length < 1) { 695 | return info.GetReturnValue().Set(length); 696 | } 697 | 698 | info.GetReturnValue().Set(Nan::NewBuffer(buffer, length, FreeBuffer, NULL).ToLocalChecked()); 699 | } 700 | 701 | NAN_METHOD(Connection::Cancel) { 702 | LOG("Connection::Cancel"); 703 | 704 | Connection* self = NODE_THIS(); 705 | 706 | PGcancel *cancelStuct = PQgetCancel(self->pq); 707 | 708 | if(cancelStuct == NULL) { 709 | info.GetReturnValue().Set(Nan::Error("Unable to allocate cancel struct")); 710 | return; 711 | } 712 | 713 | char* errBuff = new char[255]; 714 | 715 | LOG("PQcancel"); 716 | int result = PQcancel(cancelStuct, errBuff, 255); 717 | 718 | LOG("PQfreeCancel"); 719 | PQfreeCancel(cancelStuct); 720 | 721 | if(result == 1) { 722 | delete[] errBuff; 723 | return info.GetReturnValue().Set(true); 724 | } 725 | 726 | info.GetReturnValue().Set(Nan::New(errBuff).ToLocalChecked()); 727 | delete[] errBuff; 728 | } 729 | 730 | bool Connection::ConnectDB(const char* paramString) { 731 | TRACEF("Connection::ConnectDB:Connection parameters: %s\n", paramString); 732 | this->pq = PQconnectdb(paramString); 733 | 734 | ConnStatusType status = PQstatus(this->pq); 735 | 736 | if(status != CONNECTION_OK) { 737 | return false; 738 | } 739 | 740 | TRACE("Connection::Connect::Success"); 741 | return true; 742 | } 743 | 744 | 745 | void Connection::InitPollSocket() { 746 | int fd = PQsocket(this->pq); 747 | int socketInitStatus = uv_poll_init_socket(uv_default_loop(), &(this->poll_watcher), fd); 748 | 749 | if (socketInitStatus == 0) { 750 | is_success_poll_init = true; 751 | } 752 | } 753 | 754 | char * Connection::ErrorMessage() { 755 | return PQerrorMessage(this->pq); 756 | } 757 | 758 | void Connection::on_io_readable(uv_poll_t* handle, int status, int revents) { 759 | LOG("Connection::on_io_readable"); 760 | TRACEF("Connection::on_io_readable:status %d\n", status); 761 | TRACEF("Connection::on_io_readable:revents %d\n", revents); 762 | if(revents & UV_READABLE) { 763 | LOG("Connection::on_io_readable UV_READABLE"); 764 | Connection* self = (Connection*) handle->data; 765 | LOG("Got connection pointer"); 766 | self->Emit("readable"); 767 | } 768 | } 769 | 770 | void Connection::on_io_writable(uv_poll_t* handle, int status, int revents) { 771 | LOG("Connection::on_io_writable"); 772 | TRACEF("Connection::on_io_writable:status %d\n", status); 773 | TRACEF("Connection::on_io_writable:revents %d\n", revents); 774 | if(revents & UV_WRITABLE) { 775 | LOG("Connection::on_io_readable UV_WRITABLE"); 776 | Connection* self = (Connection*) handle->data; 777 | self->WriteStop(); 778 | self->Emit("writable"); 779 | } 780 | } 781 | 782 | void Connection::ReadStart() { 783 | LOG("Connection::ReadStart:starting read watcher"); 784 | is_reading = true; 785 | uv_poll_start(&poll_watcher, UV_READABLE, on_io_readable); 786 | LOG("Connection::ReadStart:started read watcher"); 787 | } 788 | 789 | void Connection::ReadStop() { 790 | LOG("Connection::ReadStop:stoping read watcher"); 791 | if(!is_reading) return; 792 | is_reading = false; 793 | uv_poll_stop(&poll_watcher); 794 | LOG("Connection::ReadStop:stopped read watcher"); 795 | } 796 | 797 | void Connection::WriteStart() { 798 | LOG("Connection::WriteStart:starting write watcher"); 799 | uv_poll_start(&poll_watcher, UV_WRITABLE, on_io_writable); 800 | LOG("Connection::WriteStart:started write watcher"); 801 | } 802 | 803 | void Connection::WriteStop() { 804 | LOG("Connection::WriteStop:stoping write watcher"); 805 | uv_poll_stop(&poll_watcher); 806 | } 807 | 808 | 809 | void Connection::ClearLastResult() { 810 | LOG("Connection::ClearLastResult"); 811 | if(lastResult == NULL) return; 812 | PQclear(lastResult); 813 | lastResult = NULL; 814 | } 815 | 816 | void Connection::SetLastResult(PGresult* result) { 817 | LOG("Connection::SetLastResult"); 818 | ClearLastResult(); 819 | lastResult = result; 820 | } 821 | 822 | char* Connection::NewCString(v8::Local val) { 823 | Nan::HandleScope scope; 824 | 825 | Nan::Utf8String str(val); 826 | char* buffer = new char[str.length() + 1]; 827 | strcpy(buffer, *str); 828 | 829 | return buffer; 830 | } 831 | 832 | char** Connection::NewCStringArray(v8::Local jsParams) { 833 | Nan::HandleScope scope; 834 | 835 | int numberOfParams = jsParams->Length(); 836 | 837 | char** parameters = new char*[numberOfParams]; 838 | 839 | for(int i = 0; i < numberOfParams; i++) { 840 | v8::Local val = Nan::Get(jsParams, i).ToLocalChecked(); 841 | if(val->IsNull()) { 842 | parameters[i] = NULL; 843 | continue; 844 | } 845 | //expect every other value to be a string... 846 | //make sure aggresive type checking is done 847 | //on the JavaScript side before calling 848 | parameters[i] = NewCString(val); 849 | } 850 | 851 | return parameters; 852 | } 853 | 854 | void Connection::DeleteCStringArray(char** array, int length) { 855 | for(int i = 0; i < length; i++) { 856 | delete [] array[i]; 857 | } 858 | delete [] array; 859 | } 860 | 861 | void Connection::Emit(const char* message) { 862 | Nan::HandleScope scope; 863 | 864 | v8::Local info[1] = { 865 | Nan::New(message).ToLocalChecked() 866 | }; 867 | 868 | TRACE("CALLING EMIT"); 869 | Nan::TryCatch tc; 870 | Nan::AsyncResource *async_emit_f = new Nan::AsyncResource("libpq:connection:emit"); 871 | async_emit_f->runInAsyncScope(handle(), "emit", 1, info); 872 | delete async_emit_f; 873 | if(tc.HasCaught()) { 874 | Nan::FatalException(tc); 875 | } 876 | } 877 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_LIBPQ_CONNECTION 2 | #define NODE_LIBPQ_CONNECTION 3 | 4 | #include 5 | #include 6 | 7 | class Connection : public Nan::ObjectWrap { 8 | public: 9 | static NAN_METHOD(Create); 10 | static NAN_METHOD(ConnectSync); 11 | static NAN_METHOD(Connect); 12 | static NAN_METHOD(ServerVersion); 13 | static NAN_METHOD(Socket); 14 | static NAN_METHOD(GetLastErrorMessage); 15 | static NAN_METHOD(Finish); 16 | static NAN_METHOD(Exec); 17 | static NAN_METHOD(ExecParams); 18 | static NAN_METHOD(Prepare); 19 | static NAN_METHOD(ExecPrepared); 20 | static NAN_METHOD(DescribePrepared); 21 | static NAN_METHOD(Clear); 22 | static NAN_METHOD(Ntuples); 23 | static NAN_METHOD(Nfields); 24 | static NAN_METHOD(Fname); 25 | static NAN_METHOD(Ftable); 26 | static NAN_METHOD(Ftablecol); 27 | static NAN_METHOD(Ftype); 28 | static NAN_METHOD(Getvalue); 29 | static NAN_METHOD(Getisnull); 30 | static NAN_METHOD(Nparams); 31 | static NAN_METHOD(Paramtype); 32 | static NAN_METHOD(CmdStatus); 33 | static NAN_METHOD(CmdTuples); 34 | static NAN_METHOD(ResultStatus); 35 | static NAN_METHOD(ResultErrorMessage); 36 | static NAN_METHOD(ResultErrorFields); 37 | static NAN_METHOD(SendQuery); 38 | static NAN_METHOD(SendQueryParams); 39 | static NAN_METHOD(SendPrepare); 40 | static NAN_METHOD(SendQueryPrepared); 41 | static NAN_METHOD(GetResult); 42 | static NAN_METHOD(ConsumeInput); 43 | static NAN_METHOD(IsBusy); 44 | static NAN_METHOD(StartRead); 45 | static NAN_METHOD(StopRead); 46 | static NAN_METHOD(StartWrite); 47 | static NAN_METHOD(SetNonBlocking); 48 | static NAN_METHOD(IsNonBlocking); 49 | static NAN_METHOD(Flush); 50 | #ifdef ESCAPE_SUPPORTED 51 | static NAN_METHOD(EscapeLiteral); 52 | static NAN_METHOD(EscapeIdentifier); 53 | #endif 54 | static NAN_METHOD(Notifies); 55 | static NAN_METHOD(PutCopyData); 56 | static NAN_METHOD(PutCopyEnd); 57 | static NAN_METHOD(GetCopyData); 58 | static NAN_METHOD(Cancel); 59 | 60 | bool ConnectDB(const char* paramString); 61 | void InitPollSocket(); 62 | char* ErrorMessage(); 63 | PGconn* pq; 64 | 65 | private: 66 | PGresult* lastResult; 67 | uv_poll_t poll_watcher; 68 | bool is_reffed; 69 | bool is_reading; 70 | bool is_success_poll_init; 71 | 72 | Connection(); 73 | 74 | static void on_io_readable(uv_poll_t* handle, int status, int revents); 75 | static void on_io_writable(uv_poll_t* handle, int status, int revents); 76 | void ReadStart(); 77 | void ReadStop(); 78 | void WriteStart(); 79 | void WriteStop(); 80 | void ClearLastResult(); 81 | void SetLastResult(PGresult* result); 82 | static char* NewCString(v8::Local val); 83 | static char** NewCStringArray(v8::Local jsParams); 84 | static void DeleteCStringArray(char** array, int length); 85 | void Emit(const char* message); 86 | }; 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /test/async-connection.js: -------------------------------------------------------------------------------- 1 | var PQ = require('../'); 2 | var assert = require('assert'); 3 | 4 | describe('async connection', function () { 5 | it('works', function (done) { 6 | var pq = new PQ(); 7 | assert(!pq.connected, 'should have connected set to falsy'); 8 | pq.connect(function (err) { 9 | if (err) { 10 | console.log(err); 11 | } 12 | assert(!err); 13 | pq.exec('SELECT NOW()'); 14 | assert.equal(pq.connected, true, 'should have connected set to true'); 15 | assert.equal(pq.ntuples(), 1); 16 | done(); 17 | }); 18 | }); 19 | 20 | it('works with hard-coded connection parameters', function (done) { 21 | var pq = new PQ(); 22 | var conString = 'host=' + (process.env.PGHOST || 'localhost'); 23 | pq.connect(conString, done); 24 | }); 25 | 26 | it('returns an error to the callback if connection fails', function (done) { 27 | new PQ().connect('host=asldkfjasldkfjalskdfjasdf', function (err) { 28 | assert(err, 'should have passed an error'); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('respects the active domain', function (done) { 34 | var pq = new PQ(); 35 | var domain = require('domain').create(); 36 | domain.run(function () { 37 | var activeDomain = process.domain; 38 | assert(activeDomain, 'Should have an active domain'); 39 | pq.connect(function (err) { 40 | assert.strictEqual( 41 | process.domain, 42 | activeDomain, 43 | 'Active domain is lost' 44 | ); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/async-socket.js: -------------------------------------------------------------------------------- 1 | var LibPQ = require('../'); 2 | var helper = require('./helper'); 3 | var assert = require('assert'); 4 | 5 | var consume = function (pq, cb) { 6 | if (!pq.isBusy()) return cb(); 7 | pq.startReader(); 8 | var onReadable = function () { 9 | assert(pq.consumeInput(), pq.errorMessage()); 10 | if (pq.isBusy()) { 11 | console.log('consuming a 2nd buffer of input later...'); 12 | return; 13 | } 14 | pq.removeListener('readable', onReadable); 15 | pq.stopReader(); 16 | cb(); 17 | }; 18 | pq.on('readable', onReadable); 19 | }; 20 | 21 | describe('async simple query', function () { 22 | helper.setupIntegration(); 23 | 24 | it('dispatches simple query', function (done) { 25 | var pq = this.pq; 26 | assert(this.pq.setNonBlocking(true)); 27 | this.pq.writable(function () { 28 | var success = pq.sendQuery('SELECT 1'); 29 | assert.strictEqual( 30 | pq.flush(), 31 | 0, 32 | 'Should have flushed all data to socket' 33 | ); 34 | assert(success, pq.errorMessage()); 35 | consume(pq, function () { 36 | assert(!pq.errorMessage()); 37 | assert(pq.getResult()); 38 | assert.strictEqual(pq.getResult(), false); 39 | assert.strictEqual(pq.ntuples(), 1); 40 | assert.strictEqual(pq.getvalue(0, 0), '1'); 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | it('dispatches parameterized query', function (done) { 47 | var pq = this.pq; 48 | var success = pq.sendQueryParams('SELECT $1::text as name', ['Brian']); 49 | assert(success, pq.errorMessage()); 50 | assert.strictEqual( 51 | pq.flush(), 52 | 0, 53 | 'Should have flushed query text & parameters' 54 | ); 55 | consume(pq, function () { 56 | assert(!pq.errorMessage()); 57 | assert(pq.getResult()); 58 | assert.strictEqual(pq.getResult(), false); 59 | assert.strictEqual(pq.ntuples(), 1); 60 | assert.equal(pq.getvalue(0, 0), 'Brian'); 61 | done(); 62 | }); 63 | }); 64 | 65 | it('throws on dispatching non-array second argument', function () { 66 | assert.throws(() => { 67 | this.pq.sendQueryParams('SELECT $1::text as name', 'Brian'); 68 | }); 69 | }); 70 | 71 | it('throws on dispatching non-array second argument to sendQueryPrepared', function () { 72 | assert.throws(() => { 73 | pq.sendQueryPrepared('test', ['Brian']); 74 | }); 75 | }); 76 | 77 | it('dispatches named query', function (done) { 78 | var pq = this.pq; 79 | var statementName = 'async-get-name'; 80 | var success = pq.sendPrepare(statementName, 'SELECT $1::text as name', 1); 81 | assert(success, pq.errorMessage()); 82 | assert.strictEqual(pq.flush(), 0, 'Should have flushed query text'); 83 | consume(pq, function () { 84 | assert(!pq.errorMessage()); 85 | 86 | //first time there should be a result 87 | assert(pq.getResult()); 88 | 89 | //call 'getResult' until it returns false indicating 90 | //there is no more input to consume 91 | assert.strictEqual(pq.getResult(), false); 92 | 93 | //since we only prepared a statement there should be 94 | //0 tuples in the result 95 | assert.equal(pq.ntuples(), 0); 96 | 97 | //now execute the previously prepared statement 98 | var success = pq.sendQueryPrepared(statementName, ['Brian']); 99 | assert(success, pq.errorMessage()); 100 | assert.strictEqual(pq.flush(), 0, 'Should have flushed parameters'); 101 | consume(pq, function () { 102 | assert(!pq.errorMessage()); 103 | 104 | //consume the result of the query execution 105 | assert(pq.getResult()); 106 | assert.equal(pq.ntuples(), 1); 107 | assert.equal(pq.getvalue(0, 0), 'Brian'); 108 | 109 | //call 'getResult' again to ensure we're finished 110 | assert.strictEqual(pq.getResult(), false); 111 | done(); 112 | }); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /test/cancel.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var assert = require('assert'); 3 | 4 | describe('cancel a request', function() { 5 | it('works', function(done) { 6 | var pq = new Libpq(); 7 | pq.connectSync(); 8 | var sent = pq.sendQuery('pg_sleep(5000)'); 9 | assert(sent, 'should have sent'); 10 | var canceled = pq.cancel(); 11 | assert.strictEqual(canceled, true, 'should have canceled'); 12 | var hasResult = pq.getResult(); 13 | assert(hasResult, 'should have a result'); 14 | assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR'); 15 | assert.equal(pq.getResult(), false); 16 | pq.exec('SELECT NOW()'); 17 | done(); 18 | }); 19 | 20 | it('returns (not throws) an error if not connected', function(done) { 21 | var pq = new Libpq(); 22 | assert.doesNotThrow(function () { 23 | pq.cancel(function (err) { 24 | assert(err, 'should raise an error when not connected'); 25 | }); 26 | }); 27 | done(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/construction.js: -------------------------------------------------------------------------------- 1 | var PQ = require('../'); 2 | var async = require('async'); 3 | 4 | describe('Constructing multiple', function() { 5 | it('works all at once', function() { 6 | for(var i = 0; i < 1000; i++) { 7 | var pq = new PQ(); 8 | } 9 | }); 10 | 11 | it('connects and disconnects each client', function(done) { 12 | var connect = function(n, cb) { 13 | var pq = new PQ(); 14 | pq.connect(cb); 15 | }; 16 | async.times(30, connect, done); 17 | }); 18 | }) 19 | -------------------------------------------------------------------------------- /test/copy-in.js: -------------------------------------------------------------------------------- 1 | var helper = require('./helper'); 2 | var assert = require('assert'); 3 | var bufferFrom = require('buffer-from') 4 | 5 | describe('COPY IN', function() { 6 | helper.setupIntegration(); 7 | 8 | it('check existing data assuptions', function() { 9 | this.pq.exec('SELECT COUNT(*) FROM test_data'); 10 | assert.equal(this.pq.getvalue(0, 0), 3); 11 | }); 12 | 13 | it('copies data in', function() { 14 | var success = this.pq.exec('COPY test_data FROM stdin'); 15 | assert.equal(this.pq.resultStatus(), 'PGRES_COPY_IN'); 16 | 17 | var buffer = bufferFrom("bob\t100\n", 'utf8'); 18 | var res = this.pq.putCopyData(buffer); 19 | assert.strictEqual(res, 1); 20 | 21 | var res = this.pq.putCopyEnd(); 22 | assert.strictEqual(res, 1); 23 | 24 | while(this.pq.getResult()) {} 25 | 26 | this.pq.exec('SELECT COUNT(*) FROM test_data'); 27 | assert.equal(this.pq.getvalue(0, 0), 4); 28 | }); 29 | 30 | it('can cancel copy data in', function() { 31 | var success = this.pq.exec('COPY test_data FROM stdin'); 32 | assert.equal(this.pq.resultStatus(), 'PGRES_COPY_IN'); 33 | 34 | var buffer = bufferFrom("bob\t100\n", 'utf8'); 35 | var res = this.pq.putCopyData(buffer); 36 | assert.strictEqual(res, 1); 37 | 38 | var res = this.pq.putCopyEnd('cancel!'); 39 | assert.strictEqual(res, 1); 40 | 41 | while(this.pq.getResult()) {} 42 | assert(this.pq.errorMessage()); 43 | assert(this.pq.errorMessage().indexOf('cancel!') > -1, this.pq.errorMessage() + ' should have contained "cancel!"'); 44 | 45 | this.pq.exec('SELECT COUNT(*) FROM test_data'); 46 | assert.equal(this.pq.getvalue(0, 0), 4); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/copy-out.js: -------------------------------------------------------------------------------- 1 | var helper = require('./helper'); 2 | var assert = require('assert'); 3 | 4 | describe('COPY OUT', function() { 5 | helper.setupIntegration(); 6 | 7 | var getRow = function(pq, expected) { 8 | var result = pq.getCopyData(false); 9 | assert(result instanceof Buffer, 'Result should be a buffer'); 10 | assert.equal(result.toString('utf8'), expected); 11 | }; 12 | 13 | it('copies data out', function() { 14 | this.pq.exec('COPY test_data TO stdin'); 15 | assert.equal(this.pq.resultStatus(), 'PGRES_COPY_OUT'); 16 | getRow(this.pq, 'brian\t32\n'); 17 | getRow(this.pq, 'aaron\t30\n'); 18 | getRow(this.pq, '\t\\N\n'); 19 | assert.strictEqual(this.pq.getCopyData(), -1); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/error-conditions.js: -------------------------------------------------------------------------------- 1 | var PQ = require('../') 2 | var assert = require('assert'); 3 | 4 | describe('without being connected', function() { 5 | it('exec fails', function() { 6 | var pq = new PQ(); 7 | pq.exec(); 8 | assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR'); 9 | assert(pq.errorMessage()); 10 | }); 11 | 12 | it('fails on async query', function() { 13 | var pq = new PQ(); 14 | var success = pq.sendQuery('blah'); 15 | assert.strictEqual(success, false); 16 | assert.equal(pq.resultStatus(), 'PGRES_FATAL_ERROR'); 17 | assert(pq.errorMessage()); 18 | }); 19 | 20 | it('throws when reading while not connected', function() { 21 | var pq = new PQ(); 22 | assert.throws(function() { 23 | pq.startReader(); 24 | }); 25 | }); 26 | 27 | it('throws when writing while not connected', function() { 28 | var pq = new PQ(); 29 | assert.throws(function() { 30 | pq.writable(function() { 31 | }); 32 | }); 33 | }); 34 | }) 35 | -------------------------------------------------------------------------------- /test/error-info.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var assert = require('assert'); 3 | var helper = require('./helper'); 4 | 5 | describe('error info', function() { 6 | helper.setupIntegration(); 7 | 8 | describe('when there is no error', function() { 9 | 10 | it('everything is null', function() { 11 | var pq = this.pq; 12 | pq.exec('SELECT NOW()'); 13 | assert(!pq.errorMessage(), pq.errorMessage()); 14 | assert.equal(pq.ntuples(), 1); 15 | assert(pq.resultErrorFields(), null); 16 | }); 17 | }); 18 | 19 | describe('when there is an error', function() { 20 | 21 | it('sets all error codes', function() { 22 | var pq = this.pq; 23 | pq.exec('INSERT INTO test_data VALUES(1, NOW())'); 24 | assert(pq.errorMessage()); 25 | var err = pq.resultErrorFields(); 26 | assert.notEqual(err, null); 27 | assert.equal(err.severity, 'ERROR'); 28 | assert.equal(err.sqlState, 42804); 29 | assert.equal(err.messagePrimary, 'column "age" is of type integer but expression is of type timestamp with time zone'); 30 | assert.equal(err.messageDetail, undefined); 31 | assert.equal(err.messageHint, 'You will need to rewrite or cast the expression.'); 32 | assert.equal(err.statementPosition, 33); 33 | assert.equal(err.internalPosition, undefined); 34 | assert.equal(err.internalQuery, undefined); 35 | assert.equal(err.context, undefined); 36 | assert.equal(err.schemaName, undefined); 37 | assert.equal(err.tableName, undefined); 38 | assert.equal(err.dataTypeName, undefined); 39 | assert.equal(err.constraintName, undefined); 40 | assert.equal(err.sourceFile, "parse_target.c"); 41 | assert(parseInt(err.sourceLine)); 42 | assert.equal(err.sourceFunction, "transformAssignedExpr"); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/escaping.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var assert = require('assert'); 3 | 4 | describe('escapeLiteral', function() { 5 | it('fails to escape when the server is not connected', function() { 6 | var pq = new Libpq(); 7 | var result = pq.escapeLiteral('test'); 8 | assert.strictEqual(result, null); 9 | assert(pq.errorMessage()); 10 | }); 11 | 12 | it('escapes a simple string', function() { 13 | var pq = new Libpq(); 14 | pq.connectSync(); 15 | var result = pq.escapeLiteral('bang'); 16 | assert.equal(result, "'bang'"); 17 | }); 18 | 19 | it('escapes a bad string', function() { 20 | var pq = new Libpq(); 21 | pq.connectSync(); 22 | var result = pq.escapeLiteral("'; TRUNCATE TABLE blah;"); 23 | assert.equal(result, "'''; TRUNCATE TABLE blah;'"); 24 | }); 25 | }); 26 | 27 | describe('escapeIdentifier', function() { 28 | it('fails when the server is not connected', function() { 29 | var pq = new Libpq(); 30 | var result = pq.escapeIdentifier('test'); 31 | assert.strictEqual(result, null); 32 | assert(pq.errorMessage()); 33 | }); 34 | 35 | it('escapes a simple string', function() { 36 | var pq = new Libpq(); 37 | pq.connectSync(); 38 | var result = pq.escapeIdentifier('bang'); 39 | assert.equal(result, '"bang"'); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | var PQ = require('../'); 2 | 3 | var createTable = function(pq) { 4 | pq.exec('CREATE TEMP TABLE test_data(name text, age int)') 5 | console.log(pq.resultErrorMessage()); 6 | pq.exec("INSERT INTO test_data(name, age) VALUES ('brian', 32), ('aaron', 30), ('', null);") 7 | }; 8 | 9 | module.exports = { 10 | setupIntegration: function() { 11 | before(function() { 12 | this.pq = new PQ(); 13 | this.pq.connectSync(); 14 | createTable(this.pq); 15 | }); 16 | 17 | after(function() { 18 | this.pq.finish(); 19 | }); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var Client = require('../') 2 | 3 | describe('connecting', function() { 4 | it('works', function() { 5 | var client = new Client(); 6 | client.connectSync(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/load.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../') 2 | var async = require('async') 3 | var ok = require('okay') 4 | 5 | var blink = function(n, cb) { 6 | var connections = [] 7 | for(var i = 0; i < 30; i++) { 8 | connections.push(new Libpq()) 9 | } 10 | var connect = function(con, cb) { 11 | con.connect(cb) 12 | } 13 | async.each(connections, connect, ok(function() { 14 | connections.forEach(function(con) { 15 | con.finish() 16 | }) 17 | cb() 18 | })) 19 | } 20 | 21 | describe('many connections', function() { 22 | it('works', function(done) { 23 | async.timesSeries(10, blink, done) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /test/many-connections.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var _ = require('lodash'); 3 | var assert = require('assert'); 4 | 5 | describe('connectSync', function() { 6 | it('works 50 times in a row', function() { 7 | var pqs = _.times(50, function() { 8 | return new Libpq(); 9 | }); 10 | pqs.forEach(function(pq) { 11 | pq.connectSync(); 12 | }); 13 | pqs.forEach(function(pq) { 14 | pq.finish(); 15 | }); 16 | }); 17 | }); 18 | 19 | //doing a bunch of stuff here to 20 | //try and shake out a hard to track down segfault 21 | describe('connect async', function() { 22 | var total = 50; 23 | it('works ' + total + ' times in a row', function(done) { 24 | var pqs = _.times(total, function() { 25 | return new Libpq(); 26 | }); 27 | 28 | var count = 0; 29 | var connect = function(cb) { 30 | pqs.forEach(function(pq) { 31 | pq.connect(function(err) { 32 | assert(!err); 33 | count++; 34 | pq.startReader(); 35 | if(count == total) { 36 | cb(); 37 | } 38 | }); 39 | }); 40 | }; 41 | connect(function() { 42 | pqs.forEach(function(pq) { 43 | pq.stopReader(); 44 | pq.finish(); 45 | }); 46 | done(); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --bail 2 | -------------------------------------------------------------------------------- /test/multiple-queries.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var ok = require('okay') 3 | 4 | var queryText = "SELECT * FROM generate_series(1, 1000)" 5 | 6 | var query = function(pq, cb) { 7 | var sent = pq.sendQuery(queryText); 8 | if(!sent) return cb(new Error(pg.errorMessage())); 9 | console.log('sent query') 10 | 11 | //consume any outstanding results 12 | //while(!pq.isBusy() && pq.getResult()) { 13 | //console.log('consumed unused result') 14 | //} 15 | 16 | var cleanup = function() { 17 | pq.removeListener('readable', onReadable); 18 | pq.stopReader(); 19 | } 20 | 21 | var readError = function(message) { 22 | cleanup(); 23 | return cb(new Error(message || pq.errorMessage)); 24 | }; 25 | 26 | var onReadable = function() { 27 | //read waiting data from the socket 28 | //e.g. clear the pending 'select' 29 | if(!pq.consumeInput()) { 30 | return readError(); 31 | } 32 | //check if there is still outstanding data 33 | //if so, wait for it all to come in 34 | if(pq.isBusy()) { 35 | return; 36 | } 37 | //load our result object 38 | pq.getResult(); 39 | 40 | //"read until results return null" 41 | //or in our case ensure we only have one result 42 | if(pq.getResult()) { 43 | return readError('Only one result at a time is accepted'); 44 | } 45 | cleanup(); 46 | return cb(null, []) 47 | }; 48 | pq.on('readable', onReadable); 49 | pq.startReader(); 50 | }; 51 | 52 | describe('multiple queries', function() { 53 | var pq = new Libpq(); 54 | 55 | before(function(done) { 56 | pq.connect(done) 57 | }) 58 | 59 | it('first query works', function(done) { 60 | query(pq, done); 61 | }); 62 | 63 | it('second query works', function(done) { 64 | query(pq, done); 65 | }); 66 | 67 | it('third query works', function(done) { 68 | query(pq, done); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/non-blocking-controls.js: -------------------------------------------------------------------------------- 1 | var helper = require('./helper') 2 | var assert = require('assert') 3 | 4 | describe('set & get non blocking', function() { 5 | helper.setupIntegration(); 6 | it('is initially set to false', function() { 7 | assert.strictEqual(this.pq.isNonBlocking(), false); 8 | }); 9 | 10 | it('can switch back and forth', function() { 11 | assert.strictEqual(this.pq.setNonBlocking(true), true); 12 | assert.strictEqual(this.pq.isNonBlocking(), true); 13 | assert.strictEqual(this.pq.setNonBlocking(), true); 14 | assert.strictEqual(this.pq.isNonBlocking(), false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/notification.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var assert = require('assert'); 3 | 4 | describe('LISTEN/NOTIFY', function() { 5 | before(function() { 6 | this.listener = new Libpq(); 7 | this.notifier = new Libpq(); 8 | this.listener.connectSync(); 9 | this.notifier.connectSync(); 10 | }); 11 | 12 | it('works', function() { 13 | this.notifier.exec("NOTIFY testing, 'My Payload'"); 14 | var notice = this.listener.notifies(); 15 | assert.equal(notice, null); 16 | 17 | this.listener.exec('LISTEN testing'); 18 | this.notifier.exec("NOTIFY testing, 'My Second Payload'"); 19 | this.listener.exec('SELECT NOW()'); 20 | var notice = this.listener.notifies(); 21 | assert(notice, 'listener should have had a notification come in'); 22 | assert.equal(notice.relname, 'testing', 'missing relname == testing'); 23 | assert.equal(notice.extra, 'My Second Payload'); 24 | assert(notice.be_pid); 25 | }); 26 | 27 | after(function() { 28 | this.listener.finish(); 29 | this.notifier.finish(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/result-accessors.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var helper = require('./helper'); 3 | 4 | describe('result accessors', function() { 5 | helper.setupIntegration(); 6 | 7 | before(function() { 8 | this.pq.exec("INSERT INTO test_data(name, age) VALUES ('bob', 80) RETURNING *"); 9 | assert(!this.pq.errorMessage()); 10 | }); 11 | 12 | it('has ntuples', function() { 13 | assert.strictEqual(this.pq.ntuples(), 1); 14 | }); 15 | 16 | it('has cmdStatus', function() { 17 | assert.equal(this.pq.cmdStatus(), 'INSERT 0 1'); 18 | }); 19 | 20 | it('has command tuples', function() { 21 | assert.strictEqual(this.pq.cmdTuples(), '1'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/server-version.js: -------------------------------------------------------------------------------- 1 | var Libpq = require('../'); 2 | var assert = require('assert'); 3 | 4 | describe('Retrieve server version from connection', function() { 5 | 6 | it('return version number when connected', function() { 7 | var pq = new Libpq(); 8 | pq.connectSync(); 9 | var version = pq.serverVersion(); 10 | assert.equal(typeof version, 'number'); 11 | assert(version > 60000); 12 | }); 13 | 14 | it('return zero when not connected', function() { 15 | var pq = new Libpq(); 16 | var version = pq.serverVersion(); 17 | assert.equal(typeof version, 'number'); 18 | assert.equal(version, 0); 19 | }); 20 | 21 | }); -------------------------------------------------------------------------------- /test/socket.js: -------------------------------------------------------------------------------- 1 | var LibPQ = require('../') 2 | var helper = require('./helper') 3 | var assert = require('assert'); 4 | 5 | describe('getting socket', function() { 6 | helper.setupIntegration(); 7 | 8 | it('returns -1 when not connected', function() { 9 | var pq = new LibPQ(); 10 | assert.equal(pq.socket(), -1); 11 | }); 12 | 13 | it('returns value when connected', function() { 14 | assert(this.pq.socket() > 0); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/sync-integration.js: -------------------------------------------------------------------------------- 1 | var PQ = require('../'); 2 | var assert = require('assert'); 3 | var helper = require('./helper'); 4 | 5 | describe('low-level query integration tests', function () { 6 | helper.setupIntegration(); 7 | 8 | describe('exec', function () { 9 | before(function () { 10 | this.pq.exec('SELECT * FROM test_data'); 11 | }); 12 | 13 | it('has correct tuples', function () { 14 | assert.strictEqual(this.pq.ntuples(), 3); 15 | }); 16 | 17 | it('has correct field count', function () { 18 | assert.strictEqual(this.pq.nfields(), 2); 19 | }); 20 | 21 | it('has correct rows', function () { 22 | assert.strictEqual(this.pq.getvalue(0, 0), 'brian'); 23 | assert.strictEqual(this.pq.getvalue(1, 1), '30'); 24 | assert.strictEqual(this.pq.getvalue(2, 0), ''); 25 | assert.strictEqual(this.pq.getisnull(2, 0), false); 26 | assert.strictEqual(this.pq.getvalue(2, 1), ''); 27 | assert.strictEqual(this.pq.getisnull(2, 1), true); 28 | }); 29 | 30 | it('has correct identifiers', function() { 31 | assert.notEqual(this.pq.ftable(0), 0); 32 | assert.notEqual(this.pq.ftable(1), 0); 33 | assert.strictEqual(this.pq.ftablecol(0), 1); 34 | assert.strictEqual(this.pq.ftablecol(1), 2); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/sync-parameters.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var helper = require('./helper'); 3 | 4 | describe('sync query with parameters', function () { 5 | helper.setupIntegration(); 6 | 7 | it('works with single string parameter', function () { 8 | var queryText = 'SELECT $1::text as name'; 9 | this.pq.execParams(queryText, ['Brian']); 10 | assert.strictEqual(this.pq.ntuples(), 1); 11 | assert.strictEqual(this.pq.getvalue(0, 0), 'Brian'); 12 | }); 13 | 14 | it('works with a number parameter', function () { 15 | var queryText = 'SELECT $1::int as age'; 16 | this.pq.execParams(queryText, [32]); 17 | assert.strictEqual(this.pq.ntuples(), 1); 18 | assert.strictEqual(this.pq.getvalue(0, 0), '32'); 19 | }); 20 | 21 | it('works with multiple parameters', function () { 22 | var queryText = 'INSERT INTO test_data(name, age) VALUES($1, $2)'; 23 | this.pq.execParams(queryText, ['Barkley', 4]); 24 | assert.equal(this.pq.resultErrorMessage(), ''); 25 | }); 26 | 27 | it('throws error when second argument is not an array', function () { 28 | var queryText = 'INSERT INTO test_data(name, age) VALUES($1, $2)'; 29 | assert.throws( 30 | function () { 31 | this.pq.execParams(queryText, 'Barkley', 4); 32 | assert.equal(this.pq.resultErrorMessage(), ''); 33 | }.bind(this) 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/sync-prepare.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var helper = require('./helper'); 3 | 4 | describe('prepare and execPrepared', function () { 5 | helper.setupIntegration(); 6 | 7 | var statementName = 'get-name'; 8 | 9 | describe('preparing a statement', function () { 10 | it('works properly', function () { 11 | this.pq.prepare(statementName, 'SELECT $1::text as name', 1); 12 | assert(!this.pq.resultErrorMessage()); 13 | assert.equal(this.pq.resultStatus(), 'PGRES_COMMAND_OK'); 14 | }); 15 | }); 16 | 17 | describe('executing a prepared statement', function () { 18 | it('works properly', function () { 19 | this.pq.execPrepared(statementName, ['Brian']); 20 | assert(!this.pq.resultErrorMessage()); 21 | assert.strictEqual(this.pq.ntuples(), 1); 22 | assert.strictEqual(this.pq.nfields(), 1); 23 | assert.strictEqual(this.pq.getvalue(0, 0), 'Brian'); 24 | }); 25 | 26 | it('throws an error when second argument is not an array', function () { 27 | assert.throws( 28 | function () { 29 | this.pq.execPrepared(statementName, 'Brian'); 30 | }.bind(this) 31 | ); 32 | }); 33 | }); 34 | 35 | describe('describing a prepared statement', function() { 36 | it('works properly', function() { 37 | this.pq.describePrepared(statementName); 38 | assert.strictEqual(this.pq.nparams(), 1) 39 | assert.strictEqual(this.pq.paramtype(0), 25) 40 | assert.strictEqual(this.pq.nfields(), 1); 41 | assert.strictEqual(this.pq.fname(0), 'name'); 42 | assert.strictEqual(this.pq.ftype(0), 25); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/sync.js: -------------------------------------------------------------------------------- 1 | var PQ = require('../') 2 | var assert = require('assert'); 3 | 4 | describe('connecting with bad credentials', function() { 5 | it('throws an error', function() { 6 | try { 7 | new PQ().connectSync('asldkfjlasdf'); 8 | } catch(e) { 9 | assert.equal(e.toString().indexOf('connection pointer is NULL'), -1) 10 | return; 11 | } 12 | assert.fail('Should have thrown an exception'); 13 | }); 14 | }); 15 | 16 | describe('connecting with no credentials', function() { 17 | before(function() { 18 | this.pq = new PQ(); 19 | this.pq.connectSync(); 20 | }); 21 | 22 | it('is connected', function() { 23 | assert(this.pq.connected, 'should have connected == true'); 24 | }); 25 | 26 | after(function() { 27 | this.pq.finish(); 28 | assert(!this.pq.connected); 29 | }); 30 | }); 31 | 32 | describe('result checking', function() { 33 | before(function() { 34 | this.pq = new PQ(); 35 | this.pq.connectSync(); 36 | }); 37 | 38 | after(function() { 39 | this.pq.finish(); 40 | }); 41 | 42 | it('executes query', function() { 43 | this.pq.exec('SELECT NOW() as my_col'); 44 | assert.equal(this.pq.resultStatus(), 'PGRES_TUPLES_OK'); 45 | }) 46 | 47 | it('has 1 tuple', function() { 48 | assert.equal(this.pq.ntuples(), 1); 49 | }); 50 | 51 | it('has 1 field', function() { 52 | assert.strictEqual(this.pq.nfields(), 1); 53 | }); 54 | 55 | it('has column name', function() { 56 | assert.equal(this.pq.fname(0), 'my_col'); 57 | }); 58 | 59 | it('has table oid zero', function() { 60 | assert.strictEqual(this.pq.ftable(0), 0); 61 | }); 62 | 63 | it('has column number of the query-result', function() { 64 | assert.strictEqual(this.pq.ftablecol(0), 0); 65 | }); 66 | 67 | it('has oid type of timestamptz', function() { 68 | assert.strictEqual(this.pq.ftype(0), 1184); 69 | }); 70 | 71 | it('has value as a date', function() { 72 | var now = new Date(); 73 | var val = this.pq.getvalue(0); 74 | var date = new Date(Date.parse(val)); 75 | assert.equal(date.getFullYear(), now.getFullYear()); 76 | assert.equal(date.getMonth(), now.getMonth()); 77 | }); 78 | 79 | it('can manually clear result multiple times', function() { 80 | this.pq.clear(); 81 | this.pq.clear(); 82 | this.pq.clear(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /vendor/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | BUILD_DIR="$(pwd)" 4 | source ./vendor/install_openssl.sh 1.1.1b 5 | sudo updatedb 6 | source ./vendor/install_libpq.sh 7 | sudo updatedb 8 | sudo ldconfig 9 | cd $BUILD_DIR 10 | -------------------------------------------------------------------------------- /vendor/install_libpq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | OPENSSL_DIR="$(pwd)/openssl-1.1.1b" 6 | POSTGRES_VERSION="11.3" 7 | POSTGRES_DIR="$(pwd)/postgres-${POSTGRES_VERSION}" 8 | TMP_DIR="/tmp/postgres" 9 | JOBS="-j$(nproc || echo 1)" 10 | 11 | if [ -d "${TMP_DIR}" ]; then 12 | rm -rf "${TMP_DIR}" 13 | fi 14 | 15 | mkdir -p "${TMP_DIR}" 16 | 17 | curl https://ftp.postgresql.org/pub/source/v${POSTGRES_VERSION}/postgresql-${POSTGRES_VERSION}.tar.gz | \ 18 | tar -C "${TMP_DIR}" -xzf - 19 | 20 | cd "${TMP_DIR}/postgresql-${POSTGRES_VERSION}" 21 | 22 | if [ -d "${POSTGRES_DIR}" ]; then 23 | rm -rf "${POSTGRES_DIR}" 24 | fi 25 | mkdir -p $POSTGRES_DIR 26 | 27 | ./configure --prefix=$POSTGRES_DIR --with-openssl --with-includes=${OPENSSL_DIR}/include --with-libraries=${OPENSSL_DIR}/lib --without-readline 28 | 29 | cd src/interfaces/libpq; make; make install; cd - 30 | cd src/bin/pg_config; make install; cd - 31 | cd src/backend; make generated-headers; cd - 32 | cd src/include; make install; cd - 33 | 34 | export PATH="${POSTGRES_DIR}/bin:${PATH}" 35 | export CFLAGS="-I${POSTGRES_DIR}/include" 36 | export LDFLAGS="-L${POSTGRES_DIR}/lib" 37 | export LD_LIBRARY_PATH="${POSTGRES_DIR}/lib:$LD_LIBRARY_PATH" 38 | export PKG_CONFIG_PATH="${POSTGRES_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH" 39 | -------------------------------------------------------------------------------- /vendor/install_openssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ${#} -lt 1 ]; then 4 | echo "OpenSSL version required." 1>&2 5 | exit 1 6 | fi 7 | 8 | OPENSSL_VERSION="${1}" 9 | OPENSSL_DIR="$(pwd)/openssl-${OPENSSL_VERSION}" 10 | TMP_DIR="/tmp/openssl" 11 | JOBS="-j$(nproc)" 12 | 13 | if [ -d "${TMP_DIR}" ]; then 14 | rm -rf "${TMP_DIR}" 15 | fi 16 | mkdir -p "${TMP_DIR}" 17 | curl -s https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz | \ 18 | tar -C "${TMP_DIR}" -xzf - 19 | pushd "${TMP_DIR}/openssl-${OPENSSL_VERSION}" 20 | if [ -d "${OPENSSL_DIR}" ]; then 21 | rm -rf "${OPENSSL_DIR}" 22 | fi 23 | ./Configure \ 24 | --prefix=${OPENSSL_DIR} \ 25 | enable-crypto-mdebug enable-crypto-mdebug-backtrace \ 26 | linux-x86_64 27 | make -s $JOBS 28 | make install_sw 29 | popd 30 | 31 | export PATH="${OPENSSL_DIR}/bin:${PATH}" 32 | export CFLAGS="-I${OPENSSL_DIR}/include" 33 | export LDFLAGS="-L${OPENSSL_DIR}/lib" 34 | export LD_LIBRARY_PATH="${OPENSSL_DIR}/lib:$LD_LIBRARY_PATH" 35 | export PKG_CONFIG_PATH="${OPENSSL_DIR}/lib/pkgconfig:$PKG_CONFIG_PATH" 36 | --------------------------------------------------------------------------------