├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Makefile ├── README.md ├── index.d.ts ├── index.js ├── index.test-d.ts ├── lib ├── binaryParsers.js ├── builtins.js └── textParsers.js ├── package.json └── test ├── index.js └── types.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | lint: 14 | timeout-minutes: 2 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | persist-credentials: false 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | - run: npm install 24 | - run: npm run lint 25 | 26 | test: 27 | timeout-minutes: 2 28 | runs-on: ubuntu-latest 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | node: 33 | - '12' 34 | - '14' 35 | - '16' 36 | - '18' 37 | - '20' 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: actions/setup-node@v4 41 | with: 42 | node-version: ${{ matrix.node }} 43 | - run: npm install 44 | - run: npm run test-js 45 | 46 | types: 47 | timeout-minutes: 2 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: actions/setup-node@v4 52 | with: 53 | node-version: '20' 54 | - run: npm install 55 | - run: npm run test-ts 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules 3 | .nyc_output/ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: coverage minor patch test 2 | 3 | test: 4 | npm test 5 | 6 | lint: 7 | npm run lint 8 | 9 | coverage: 10 | npm run coverage 11 | 12 | patch: test 13 | npm version patch -m "Bump version" 14 | git push origin master --tags 15 | npm publish 16 | 17 | minor: test 18 | npm version minor -m "Bump version" 19 | git push origin master --tags 20 | npm publish 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pg-types 2 | 3 | This is the code that turns all the raw text from postgres into JavaScript types for [node-postgres](https://github.com/brianc/node-postgres.git) 4 | 5 | ## use 6 | 7 | This module is consumed and exported from the root `pg` object of node-postgres. To access it, do the following: 8 | 9 | ```js 10 | var types = require('pg').types 11 | ``` 12 | 13 | Generally what you'll want to do is override how a specific data-type is parsed and turned into a JavaScript type. By default the PostgreSQL backend server returns everything as strings. Every data type corresponds to a unique `OID` within the server, and these `OIDs` are sent back with the query response. So, you need to match a particluar `OID` to a function you'd like to use to take the raw text input and produce a valid JavaScript object as a result. `null` values are never parsed. 14 | 15 | Let's do something I commonly like to do on projects: return 64-bit integers `(int8)` as JavaScript integers. Because JavaScript doesn't have support for 64-bit integers node-postgres cannot confidently parse `int8` data type results as numbers because if you have a _huge_ number it will overflow and the result you'd get back from node-postgres would not be the result in the database. That would be a __very bad thing__ so node-postgres just returns `int8` results as strings and leaves the parsing up to you. Let's say that you know you don't and wont ever have numbers greater than `int4` in your database, but you're tired of receiving results from the `COUNT(*)` function as strings (because that function returns `int8`). You would do this: 16 | 17 | ```js 18 | var types = require('pg').types 19 | types.setTypeParser(20, function(val) { 20 | return parseInt(val, 10) 21 | }) 22 | ``` 23 | 24 | __boom__: now you get numbers instead of strings. 25 | 26 | Just as another example -- not saying this is a good idea -- let's say you want to return all dates from your database as [moment](http://momentjs.com/docs/) objects. Okay, do this: 27 | 28 | ```js 29 | var types = require('pg').types 30 | var moment = require('moment') 31 | var parseFn = function(val) { 32 | return val === null ? null : moment(val) 33 | } 34 | types.setTypeParser(types.builtins.TIMESTAMPTZ, parseFn) 35 | types.setTypeParser(types.builtins.TIMESTAMP, parseFn) 36 | ``` 37 | _note: I've never done that with my dates, and I'm not 100% sure moment can parse all the date strings returned from postgres. It's just an example!_ 38 | 39 | If you're thinking "gee, this seems pretty handy, but how can I get a list of all the OIDs in the database and what they correspond to?!?!?!" worry not: 40 | 41 | ```bash 42 | $ psql -c "select typname, oid, typarray from pg_type order by oid" 43 | ``` 44 | 45 | If you want to find out the OID of a specific type: 46 | 47 | ```bash 48 | $ psql -c "select typname, oid, typarray from pg_type where typname = 'daterange' order by oid" 49 | ``` 50 | 51 | :smile: 52 | 53 | ## license 54 | 55 | The MIT License (MIT) 56 | 57 | Copyright (c) 2014 Brian M. Carlson 58 | 59 | Permission is hereby granted, free of charge, to any person obtaining a copy 60 | of this software and associated documentation files (the "Software"), to deal 61 | in the Software without restriction, including without limitation the rights 62 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 63 | copies of the Software, and to permit persons to whom the Software is 64 | furnished to do so, subject to the following conditions: 65 | 66 | The above copyright notice and this permission notice shall be included in 67 | all copies or substantial portions of the Software. 68 | 69 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 70 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 71 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 72 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 73 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 74 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 75 | THE SOFTWARE. 76 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export enum builtins { 2 | BOOL = 16, 3 | BYTEA = 17, 4 | CHAR = 18, 5 | INT8 = 20, 6 | INT2 = 21, 7 | INT4 = 23, 8 | REGPROC = 24, 9 | TEXT = 25, 10 | OID = 26, 11 | TID = 27, 12 | XID = 28, 13 | CID = 29, 14 | JSON = 114, 15 | XML = 142, 16 | PG_NODE_TREE = 194, 17 | SMGR = 210, 18 | PATH = 602, 19 | POLYGON = 604, 20 | CIDR = 650, 21 | FLOAT4 = 700, 22 | FLOAT8 = 701, 23 | ABSTIME = 702, 24 | RELTIME = 703, 25 | TINTERVAL = 704, 26 | CIRCLE = 718, 27 | MACADDR8 = 774, 28 | MONEY = 790, 29 | MACADDR = 829, 30 | INET = 869, 31 | ACLITEM = 1033, 32 | BPCHAR = 1042, 33 | VARCHAR = 1043, 34 | DATE = 1082, 35 | TIME = 1083, 36 | TIMESTAMP = 1114, 37 | TIMESTAMPTZ = 1184, 38 | INTERVAL = 1186, 39 | TIMETZ = 1266, 40 | BIT = 1560, 41 | VARBIT = 1562, 42 | NUMERIC = 1700, 43 | REFCURSOR = 1790, 44 | REGPROCEDURE = 2202, 45 | REGOPER = 2203, 46 | REGOPERATOR = 2204, 47 | REGCLASS = 2205, 48 | REGTYPE = 2206, 49 | UUID = 2950, 50 | TXID_SNAPSHOT = 2970, 51 | PG_LSN = 3220, 52 | PG_NDISTINCT = 3361, 53 | PG_DEPENDENCIES = 3402, 54 | TSVECTOR = 3614, 55 | TSQUERY = 3615, 56 | GTSVECTOR = 3642, 57 | REGCONFIG = 3734, 58 | REGDICTIONARY = 3769, 59 | JSONB = 3802, 60 | REGNAMESPACE = 4089, 61 | REGROLE = 4096 62 | } 63 | 64 | export type TypeId = builtins; 65 | export type TypesBuiltins = typeof builtins; 66 | 67 | export type TypeParser = (value: I) => T; 68 | 69 | export function setTypeParser(oid: number | TypeId, parseFn: TypeParser): void; 70 | export function setTypeParser(oid: number | TypeId, format: 'text', parseFn: TypeParser): void; 71 | export function setTypeParser(oid: number | TypeId, format: 'binary', parseFn: TypeParser): void; 72 | 73 | export function getTypeParser(oid: number | TypeId): TypeParser; 74 | export function getTypeParser(oid: number | TypeId, format: 'text'): TypeParser; 75 | export function getTypeParser(oid: number | TypeId, format: 'binary'): TypeParser; 76 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const textParsers = require('./lib/textParsers') 2 | const binaryParsers = require('./lib/binaryParsers') 3 | const builtinTypes = require('./lib/builtins') 4 | 5 | exports.getTypeParser = getTypeParser 6 | exports.setTypeParser = setTypeParser 7 | exports.builtins = builtinTypes 8 | 9 | const typeParsers = { 10 | text: {}, 11 | binary: {} 12 | } 13 | 14 | // the empty parse function 15 | const noParse = String 16 | 17 | // returns a function used to convert a specific type (specified by 18 | // oid) into a result javascript type 19 | // note: the oid can be obtained via the following sql query: 20 | // SELECT oid FROM pg_type WHERE typname = 'TYPE_NAME_HERE'; 21 | function getTypeParser (oid, format) { 22 | format = format || 'text' 23 | if (!typeParsers[format]) { 24 | return noParse 25 | } 26 | return typeParsers[format][oid] || noParse 27 | }; 28 | 29 | function setTypeParser (oid, format, parseFn) { 30 | if (typeof format === 'function') { 31 | parseFn = format 32 | format = 'text' 33 | } 34 | if (!Number.isInteger(oid)) { 35 | throw new TypeError('oid must be an integer: ' + oid) 36 | } 37 | typeParsers[format][oid] = parseFn 38 | }; 39 | 40 | textParsers.init(function (oid, converter) { 41 | typeParsers.text[oid] = converter 42 | }) 43 | 44 | binaryParsers.init(function (oid, converter) { 45 | typeParsers.binary[oid] = converter 46 | }) 47 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import * as types from '.'; 2 | import {expectType} from 'tsd'; 3 | 4 | // builtins 5 | expectType(types.builtins); 6 | 7 | // getTypeParser - not existing parser 8 | const noParse1 = types.getTypeParser(types.builtins.NUMERIC); 9 | expectType(noParse1('noParse')); 10 | const noParse2 = types.getTypeParser(types.builtins.NUMERIC, 'text'); 11 | expectType(noParse2('noParse')); 12 | const noParse3 = types.getTypeParser(types.builtins.BOOL, 'binary'); 13 | expectType(noParse3(Buffer.from([]))); 14 | 15 | // getTypeParser - existing parser 16 | const booleanParser1 = types.getTypeParser(types.builtins.BOOL); 17 | expectType(booleanParser1('t')); 18 | const booleanParser2 = types.getTypeParser(types.builtins.BOOL, 'text'); 19 | expectType(booleanParser2('f')); 20 | const numericParser = types.getTypeParser(types.builtins.NUMERIC, 'binary'); 21 | expectType(numericParser(Buffer.from([200, 1, 0, 15]))); 22 | 23 | // setTypeParser 24 | types.setTypeParser(types.builtins.INT8, Number.parseInt); 25 | types.setTypeParser(types.builtins.FLOAT8, Number.parseFloat); 26 | types.setTypeParser(types.builtins.FLOAT8, 'binary', (data: Buffer): number => data[0]); 27 | types.setTypeParser(types.builtins.FLOAT8, 'text', Number.parseFloat); 28 | -------------------------------------------------------------------------------- /lib/binaryParsers.js: -------------------------------------------------------------------------------- 1 | const parseInt64 = require('pg-int8') 2 | const parseNumeric = require('pg-numeric') 3 | 4 | const parseInt16 = function (value) { 5 | return value.readInt16BE(0) 6 | } 7 | 8 | const parseInt32 = function (value) { 9 | return value.readInt32BE(0) 10 | } 11 | 12 | const parseFloat32 = function (value) { 13 | return value.readFloatBE(0) 14 | } 15 | 16 | const parseFloat64 = function (value) { 17 | return value.readDoubleBE(0) 18 | } 19 | 20 | const parseTimestampUTC = function (value) { 21 | const rawValue = 0x100000000 * value.readInt32BE(0) + value.readUInt32BE(4) 22 | 23 | // discard usecs and shift from 2000 to 1970 24 | const result = new Date(Math.round(rawValue / 1000) + 946684800000) 25 | 26 | return result 27 | } 28 | 29 | const parseArray = function (value) { 30 | const dim = value.readInt32BE(0) 31 | 32 | const elementType = value.readUInt32BE(8) 33 | 34 | let offset = 12 35 | const dims = [] 36 | for (let i = 0; i < dim; i++) { 37 | // parse dimension 38 | dims[i] = value.readInt32BE(offset) 39 | offset += 4 40 | 41 | // ignore lower bounds 42 | offset += 4 43 | } 44 | 45 | const parseElement = function (elementType) { 46 | // parse content length 47 | const length = value.readInt32BE(offset) 48 | offset += 4 49 | 50 | // parse null values 51 | if (length === -1) { 52 | return null 53 | } 54 | 55 | let result 56 | if (elementType === 0x17) { 57 | // int 58 | result = value.readInt32BE(offset) 59 | offset += length 60 | return result 61 | } else if (elementType === 0x14) { 62 | // bigint 63 | result = parseInt64(value.slice(offset, offset += length)) 64 | return result 65 | } else if (elementType === 0x19) { 66 | // string 67 | result = value.toString('utf8', offset, offset += length) 68 | return result 69 | } else { 70 | throw new Error('ElementType not implemented: ' + elementType) 71 | } 72 | } 73 | 74 | const parse = function (dimension, elementType) { 75 | const array = [] 76 | let i 77 | 78 | if (dimension.length > 1) { 79 | const count = dimension.shift() 80 | for (i = 0; i < count; i++) { 81 | array[i] = parse(dimension, elementType) 82 | } 83 | dimension.unshift(count) 84 | } else { 85 | for (i = 0; i < dimension[0]; i++) { 86 | array[i] = parseElement(elementType) 87 | } 88 | } 89 | 90 | return array 91 | } 92 | 93 | return parse(dims, elementType) 94 | } 95 | 96 | const parseText = function (value) { 97 | return value.toString('utf8') 98 | } 99 | 100 | const parseBool = function (value) { 101 | return value[0] !== 0 102 | } 103 | 104 | const init = function (register) { 105 | register(20, parseInt64) 106 | register(21, parseInt16) 107 | register(23, parseInt32) 108 | register(26, parseInt32) 109 | register(1700, parseNumeric) 110 | register(700, parseFloat32) 111 | register(701, parseFloat64) 112 | register(16, parseBool) 113 | register(1114, parseTimestampUTC) 114 | register(1184, parseTimestampUTC) 115 | register(1000, parseArray) 116 | register(1007, parseArray) 117 | register(1016, parseArray) 118 | register(1008, parseArray) 119 | register(1009, parseArray) 120 | register(25, parseText) 121 | } 122 | 123 | module.exports = { 124 | init: init 125 | } 126 | -------------------------------------------------------------------------------- /lib/builtins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Following query was used to generate this file: 3 | 4 | SELECT json_object_agg(UPPER(PT.typname), PT.oid::int4 ORDER BY pt.oid) 5 | FROM pg_type PT 6 | WHERE typnamespace = (SELECT pgn.oid FROM pg_namespace pgn WHERE nspname = 'pg_catalog') -- Take only builting Postgres types with stable OID (extension types are not guaranted to be stable) 7 | AND typtype = 'b' -- Only basic types 8 | AND typelem = 0 -- Ignore aliases 9 | AND typisdefined -- Ignore undefined types 10 | */ 11 | 12 | module.exports = { 13 | BOOL: 16, 14 | BYTEA: 17, 15 | CHAR: 18, 16 | INT8: 20, 17 | INT2: 21, 18 | INT4: 23, 19 | REGPROC: 24, 20 | TEXT: 25, 21 | OID: 26, 22 | TID: 27, 23 | XID: 28, 24 | CID: 29, 25 | JSON: 114, 26 | XML: 142, 27 | PG_NODE_TREE: 194, 28 | SMGR: 210, 29 | PATH: 602, 30 | POLYGON: 604, 31 | CIDR: 650, 32 | FLOAT4: 700, 33 | FLOAT8: 701, 34 | ABSTIME: 702, 35 | RELTIME: 703, 36 | TINTERVAL: 704, 37 | CIRCLE: 718, 38 | MACADDR8: 774, 39 | MONEY: 790, 40 | MACADDR: 829, 41 | INET: 869, 42 | ACLITEM: 1033, 43 | BPCHAR: 1042, 44 | VARCHAR: 1043, 45 | DATE: 1082, 46 | TIME: 1083, 47 | TIMESTAMP: 1114, 48 | TIMESTAMPTZ: 1184, 49 | INTERVAL: 1186, 50 | TIMETZ: 1266, 51 | BIT: 1560, 52 | VARBIT: 1562, 53 | NUMERIC: 1700, 54 | REFCURSOR: 1790, 55 | REGPROCEDURE: 2202, 56 | REGOPER: 2203, 57 | REGOPERATOR: 2204, 58 | REGCLASS: 2205, 59 | REGTYPE: 2206, 60 | UUID: 2950, 61 | TXID_SNAPSHOT: 2970, 62 | PG_LSN: 3220, 63 | PG_NDISTINCT: 3361, 64 | PG_DEPENDENCIES: 3402, 65 | TSVECTOR: 3614, 66 | TSQUERY: 3615, 67 | GTSVECTOR: 3642, 68 | REGCONFIG: 3734, 69 | REGDICTIONARY: 3769, 70 | JSONB: 3802, 71 | REGNAMESPACE: 4089, 72 | REGROLE: 4096 73 | } 74 | -------------------------------------------------------------------------------- /lib/textParsers.js: -------------------------------------------------------------------------------- 1 | const array = require('postgres-array') 2 | const parseTimestampTz = require('postgres-date') 3 | const parseInterval = require('postgres-interval') 4 | const parseByteA = require('postgres-bytea') 5 | const range = require('postgres-range') 6 | 7 | function parseBool (value) { 8 | return value === 'TRUE' || 9 | value === 't' || 10 | value === 'true' || 11 | value === 'y' || 12 | value === 'yes' || 13 | value === 'on' || 14 | value === '1' 15 | } 16 | 17 | function parseBoolArray (value) { 18 | return array.parse(value, parseBool) 19 | } 20 | 21 | function parseIntegerArray (value) { 22 | return array.parse(value, Number) 23 | } 24 | 25 | function parseBigIntegerArray (value) { 26 | return array.parse(value, function (entry) { 27 | return parseBigInteger(entry).trim() 28 | }) 29 | } 30 | 31 | const parsePointArray = function (value) { 32 | return array.parse(value, parsePoint) 33 | } 34 | 35 | const parseFloatArray = function (value) { 36 | return array.parse(value, parseFloat) 37 | } 38 | 39 | const parseStringArray = function (value) { 40 | return array.parse(value, undefined) 41 | } 42 | 43 | const parseTimestamp = function (value) { 44 | const utc = value.endsWith(' BC') 45 | ? value.slice(0, -3) + 'Z BC' 46 | : value + 'Z' 47 | 48 | return parseTimestampTz(utc) 49 | } 50 | 51 | const parseTimestampArray = function (value) { 52 | return array.parse(value, parseTimestamp) 53 | } 54 | 55 | const parseTimestampTzArray = function (value) { 56 | return array.parse(value, parseTimestampTz) 57 | } 58 | 59 | const parseIntervalArray = function (value) { 60 | return array.parse(value, parseInterval) 61 | } 62 | 63 | const parseByteAArray = function (value) { 64 | return array.parse(value, parseByteA) 65 | } 66 | 67 | const parseBigInteger = function (value) { 68 | const valStr = String(value) 69 | if (/^\d+$/.test(valStr)) { return valStr } 70 | return value 71 | } 72 | 73 | const parseJsonArray = function (value) { 74 | return array.parse(value, JSON.parse) 75 | } 76 | 77 | const parsePoint = function (value) { 78 | if (value[0] !== '(') { return null } 79 | 80 | value = value.substring(1, value.length - 1).split(',') 81 | 82 | return { 83 | x: parseFloat(value[0]), 84 | y: parseFloat(value[1]) 85 | } 86 | } 87 | 88 | const parseCircle = function (value) { 89 | if (value[0] !== '<' && value[1] !== '(') { return null } 90 | 91 | let point = '(' 92 | let radius = '' 93 | let pointParsed = false 94 | for (let i = 2; i < value.length - 1; i++) { 95 | if (!pointParsed) { 96 | point += value[i] 97 | } 98 | 99 | if (value[i] === ')') { 100 | pointParsed = true 101 | continue 102 | } else if (!pointParsed) { 103 | continue 104 | } 105 | 106 | if (value[i] === ',') { 107 | continue 108 | } 109 | 110 | radius += value[i] 111 | } 112 | const result = parsePoint(point) 113 | result.radius = parseFloat(radius) 114 | 115 | return result 116 | } 117 | 118 | function parseInt4Range (raw) { 119 | return range.parse(raw, Number) 120 | } 121 | 122 | function parseNumRange (raw) { 123 | return range.parse(raw, parseFloat) 124 | } 125 | 126 | function parseInt8Range (raw) { 127 | return range.parse(raw, parseBigInteger) 128 | } 129 | 130 | function parseTimestampRange (raw) { 131 | return range.parse(raw, parseTimestamp) 132 | } 133 | 134 | function parseTimestampTzRange (raw) { 135 | return range.parse(raw, parseTimestampTz) 136 | } 137 | 138 | const init = function (register) { 139 | register(20, parseBigInteger) // int8 140 | register(21, Number) // int2 141 | register(23, Number) // int4 142 | register(26, Number) // oid 143 | register(700, parseFloat) // float4/real 144 | register(701, parseFloat) // float8/double 145 | register(16, parseBool) 146 | register(1114, parseTimestamp) // timestamp without time zone 147 | register(1184, parseTimestampTz) // timestamp with time zone 148 | register(600, parsePoint) // point 149 | register(651, parseStringArray) // cidr[] 150 | register(718, parseCircle) // circle 151 | register(1000, parseBoolArray) 152 | register(1001, parseByteAArray) 153 | register(1005, parseIntegerArray) // _int2 154 | register(1007, parseIntegerArray) // _int4 155 | register(1028, parseIntegerArray) // oid[] 156 | register(1016, parseBigIntegerArray) // _int8 157 | register(1017, parsePointArray) // point[] 158 | register(1021, parseFloatArray) // _float4 159 | register(1022, parseFloatArray) // _float8 160 | register(1231, parseStringArray) // _numeric 161 | register(1014, parseStringArray) // char 162 | register(1015, parseStringArray) // varchar 163 | register(1008, parseStringArray) 164 | register(1009, parseStringArray) 165 | register(1040, parseStringArray) // macaddr[] 166 | register(1041, parseStringArray) // inet[] 167 | register(1115, parseTimestampArray) // timestamp without time zone[] 168 | register(1182, parseStringArray) // date[] 169 | register(1185, parseTimestampTzArray) // timestamp with time zone[] 170 | register(1186, parseInterval) 171 | register(1187, parseIntervalArray) 172 | register(17, parseByteA) 173 | register(114, JSON.parse) // json 174 | register(3802, JSON.parse) // jsonb 175 | register(199, parseJsonArray) // json[] 176 | register(3807, parseJsonArray) // jsonb[] 177 | register(3904, parseInt4Range) // int4range 178 | register(3906, parseNumRange) // numrange 179 | register(3907, parseStringArray) // numrange[] 180 | register(3908, parseTimestampRange) // tsrange 181 | register(3910, parseTimestampTzRange) // tstzrange 182 | register(3912, range.parse) // daterange 183 | register(3926, parseInt8Range) // int8range 184 | register(2951, parseStringArray) // uuid[] 185 | register(791, parseStringArray) // money[] 186 | register(1183, parseStringArray) // time[] 187 | register(1270, parseStringArray) // timetz[] 188 | } 189 | 190 | module.exports = { 191 | init: init 192 | } 193 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg-types", 3 | "version": "4.0.2", 4 | "description": "Query result type converters for node-postgres", 5 | "main": "index.js", 6 | "scripts": { 7 | "coverage": "nyc --reporter=html npm test && open-cli coverage/index.html", 8 | "coverage-ci": "nyc --reporter=lcov npm test && codecov", 9 | "lint": "standard", 10 | "test": "npm run test-js && npm run test-ts && npm run lint", 11 | "test-js": "tape test/*.js | tap-spec", 12 | "test-ts": "tsd" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/brianc/node-pg-types.git" 17 | }, 18 | "keywords": [ 19 | "postgres", 20 | "PostgreSQL", 21 | "pg" 22 | ], 23 | "author": "Brian M. Carlson", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/brianc/node-pg-types/issues" 27 | }, 28 | "homepage": "https://github.com/brianc/node-pg-types", 29 | "devDependencies": { 30 | "@types/node": "^14.14.33", 31 | "codecov": "^3.8.1", 32 | "nyc": "^15.1.0", 33 | "open-cli": "^6.0.1", 34 | "standard": "^16.0.3", 35 | "tap-spec": "^5.0.0", 36 | "tape": "^5.2.2", 37 | "tsd": "^0.14.0" 38 | }, 39 | "dependencies": { 40 | "pg-int8": "1.0.1", 41 | "pg-numeric": "1.0.2", 42 | "postgres-array": "~3.0.1", 43 | "postgres-bytea": "~3.0.0", 44 | "postgres-date": "~2.1.0", 45 | "postgres-interval": "^3.0.0", 46 | "postgres-range": "^1.1.1" 47 | }, 48 | "files": [ 49 | "index.js", 50 | "index.d.ts", 51 | "lib" 52 | ], 53 | "engines": { 54 | "node": ">=10" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | const test = require('tape') 3 | const { getTypeParser, setTypeParser } = require('../') 4 | const types = require('./types') 5 | 6 | test('types', function (t) { 7 | Object.keys(types).forEach(function (typeName) { 8 | const type = types[typeName] 9 | t.test(typeName, function (t) { 10 | const parser = getTypeParser(type.id, type.format) 11 | type.tests.forEach(function (tests) { 12 | let input = tests[0] 13 | if (type.format === 'binary' && input !== null && !Buffer.isBuffer(input)) { 14 | if (Array.isArray(input) || typeof (input) === 'string') { 15 | input = Buffer.from(input) 16 | } else { 17 | throw new Error('Binary test inputs must be null, a String, a Buffer, or an Array') 18 | } 19 | } 20 | const expected = tests[1] 21 | const result = parser(input) 22 | if (typeof expected === 'function') { 23 | return expected(t, result) 24 | } 25 | t.equal(result, expected) 26 | }) 27 | t.end() 28 | }) 29 | }) 30 | 31 | t.test('binary array bits overflow', function (t) { 32 | const parser = getTypeParser(1009, 'binary') 33 | 34 | const expected = 'a'.repeat(1000000) 35 | 36 | const input = Buffer.alloc(20 + 300 * (4 + expected.length)) 37 | input.write( 38 | '\x00\x00\x00\x01' + // 1 dimension 39 | '\x00\x00\x00\x00' + // no nulls 40 | '\x00\x00\x00\x19' + // text[] 41 | '\x00\x00\x01\x2c' + // 300 elements 42 | '\x00\x00\x00\x01', // lower bound 1 43 | 0, 44 | 'binary' 45 | ) 46 | 47 | for (let offset = 20; offset < input.length; offset += 4 + expected.length) { 48 | input.write('\x00\x0f\x42\x40', offset, 'binary') 49 | input.write(expected, offset + 4, 'utf8') 50 | } 51 | 52 | const result = parser(input) 53 | 54 | t.equal(result.length, 300) 55 | 56 | let correct = 0 57 | 58 | result.forEach(function (element) { 59 | if (element === expected) { 60 | correct++ 61 | } 62 | }) 63 | 64 | t.equal(correct, 300) 65 | t.end() 66 | }) 67 | 68 | t.test('setTypeParser should throw when oid is not an integer', function (t) { 69 | t.throws(function () { 70 | setTypeParser(null, function () {}) 71 | }, /^TypeError: oid must be an integer/) 72 | t.throws(function () { 73 | setTypeParser('a', function () {}) 74 | }, /^TypeError: oid must be an integer/) 75 | t.end() 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /test/types.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const PostgresInterval = require('postgres-interval') 3 | 4 | const { 5 | Range, 6 | RANGE_EMPTY, 7 | RANGE_LB_INC, 8 | RANGE_LB_INF, 9 | RANGE_UB_INF, 10 | RANGE_UB_INC 11 | } = require('postgres-range') 12 | 13 | exports['string/varchar'] = { 14 | format: 'text', 15 | id: 1043, 16 | tests: [ 17 | ['bang', 'bang'] 18 | ] 19 | } 20 | 21 | exports['integer/int4'] = { 22 | format: 'text', 23 | id: 23, 24 | tests: [ 25 | ['2147483647', 2147483647] 26 | ] 27 | } 28 | 29 | exports['smallint/int2'] = { 30 | format: 'text', 31 | id: 21, 32 | tests: [ 33 | ['32767', 32767] 34 | ] 35 | } 36 | 37 | exports['bigint/int8'] = { 38 | format: 'text', 39 | id: 20, 40 | tests: [ 41 | ['9223372036854775807', '9223372036854775807'] 42 | ] 43 | } 44 | 45 | exports.oid = { 46 | format: 'text', 47 | id: 26, 48 | tests: [ 49 | ['103', 103] 50 | ] 51 | } 52 | 53 | const bignum = '31415926535897932384626433832795028841971693993751058.16180339887498948482045868343656381177203091798057628' 54 | exports.numeric = { 55 | format: 'text', 56 | id: 1700, 57 | tests: [ 58 | [bignum, bignum] 59 | ] 60 | } 61 | 62 | exports['real/float4'] = { 63 | format: 'text', 64 | id: 700, 65 | tests: [ 66 | ['123.456', 123.456] 67 | ] 68 | } 69 | 70 | exports['double precision / float 8'] = { 71 | format: 'text', 72 | id: 701, 73 | tests: [ 74 | ['12345678.12345678', 12345678.12345678] 75 | ] 76 | } 77 | 78 | exports.boolean = { 79 | format: 'text', 80 | id: 16, 81 | tests: [ 82 | ['TRUE', true], 83 | ['t', true], 84 | ['true', true], 85 | ['y', true], 86 | ['yes', true], 87 | ['on', true], 88 | ['1', true], 89 | ['f', false] 90 | ] 91 | } 92 | 93 | exports.timestamptz = { 94 | format: 'text', 95 | id: 1184, 96 | tests: [ 97 | [ 98 | '2010-10-31 14:54:13.74-05:30', 99 | dateEquals(2010, 9, 31, 20, 24, 13, 740) 100 | ], 101 | [ 102 | '2011-01-23 22:05:00.68-06', 103 | dateEquals(2011, 0, 24, 4, 5, 0, 680) 104 | ], 105 | [ 106 | '2010-10-30 14:11:12.730838Z', 107 | dateEquals(2010, 9, 30, 14, 11, 12, 730) 108 | ], 109 | [ 110 | '2010-10-30 13:10:01+05', 111 | dateEquals(2010, 9, 30, 8, 10, 1, 0) 112 | ], 113 | [ 114 | '1000-01-01 00:00:00+00 BC', 115 | dateEquals(-999, 0, 1, 0, 0, 0, 0) 116 | ] 117 | ] 118 | } 119 | 120 | exports.timestamp = { 121 | format: 'text', 122 | id: 1114, 123 | tests: [ 124 | [ 125 | '2010-10-31 00:00:00', 126 | function (t, value) { 127 | t.equal( 128 | value.toISOString(), 129 | '2010-10-31T00:00:00.000Z' 130 | ) 131 | } 132 | ], 133 | [ 134 | '1000-01-01 00:00:00 BC', 135 | function (t, value) { 136 | t.equal( 137 | value.toISOString(), 138 | '-000999-01-01T00:00:00.000Z' 139 | ) 140 | } 141 | ] 142 | ] 143 | } 144 | 145 | exports.date = { 146 | format: 'text', 147 | id: 1082, 148 | tests: [ 149 | ['2010-10-31', '2010-10-31'], 150 | ['2010-10-31 BC', '2010-10-31 BC'] 151 | ] 152 | } 153 | 154 | exports.inet = { 155 | format: 'text', 156 | id: 869, 157 | tests: [ 158 | ['8.8.8.8', '8.8.8.8'], 159 | ['2001:4860:4860::8888', '2001:4860:4860::8888'], 160 | ['127.0.0.1', '127.0.0.1'], 161 | ['fd00:1::40e', 'fd00:1::40e'], 162 | ['1.2.3.4', '1.2.3.4'] 163 | ] 164 | } 165 | 166 | exports.cidr = { 167 | format: 'text', 168 | id: 650, 169 | tests: [ 170 | ['172.16.0.0/12', '172.16.0.0/12'], 171 | ['fe80::/10', 'fe80::/10'], 172 | ['fc00::/7', 'fc00::/7'], 173 | ['192.168.0.0/24', '192.168.0.0/24'], 174 | ['10.0.0.0/8', '10.0.0.0/8'] 175 | ] 176 | } 177 | 178 | exports.macaddr = { 179 | format: 'text', 180 | id: 829, 181 | tests: [ 182 | ['08:00:2b:01:02:03', '08:00:2b:01:02:03'], 183 | ['16:10:9f:0d:66:00', '16:10:9f:0d:66:00'] 184 | ] 185 | } 186 | 187 | exports.numrange = { 188 | format: 'text', 189 | id: 3906, 190 | tests: [ 191 | ['empty', function (t, value) { 192 | t.deepEqual(value, new Range(null, null, RANGE_EMPTY)) 193 | }], 194 | ['(,)', function (t, value) { 195 | t.deepEqual(value, new Range(null, null, RANGE_LB_INF | RANGE_UB_INF)) 196 | }], 197 | ['(1.5,)', function (t, value) { 198 | t.deepEqual(value, new Range(1.5, null, RANGE_UB_INF)) 199 | }], 200 | ['(,1.5)', function (t, value) { 201 | t.deepEqual(value, new Range(null, 1.5, RANGE_LB_INF)) 202 | }], 203 | ['(0,5)', function (t, value) { 204 | t.deepEqual(value, new Range(0, 5, 0)) 205 | }], 206 | ['(,1.5]', function (t, value) { 207 | t.deepEqual(value, new Range(null, 1.5, RANGE_LB_INF | RANGE_UB_INC)) 208 | }], 209 | ['[1.5,)', function (t, value) { 210 | t.deepEqual(value, new Range(1.5, null, RANGE_LB_INC | RANGE_UB_INF)) 211 | }], 212 | ['[0,0.5)', function (t, value) { 213 | t.deepEqual(value, new Range(0, 0.5, RANGE_LB_INC)) 214 | }], 215 | ['(0,0.5]', function (t, value) { 216 | t.deepEqual(value, new Range(0, 0.5, RANGE_UB_INC)) 217 | }], 218 | ['[0,0.5]', function (t, value) { 219 | t.deepEqual(value, new Range(0, 0.5, RANGE_LB_INC | RANGE_UB_INC)) 220 | }] 221 | ] 222 | } 223 | 224 | exports.int4range = { 225 | format: 'text', 226 | id: 3904, 227 | tests: [ 228 | ['empty', function (t, value) { 229 | t.deepEqual(value, new Range(null, null, RANGE_EMPTY)) 230 | }], 231 | ['(,)', function (t, value) { 232 | t.deepEqual(value, new Range(null, null, RANGE_LB_INF | RANGE_UB_INF)) 233 | }], 234 | ['(1,)', function (t, value) { 235 | t.deepEqual(value, new Range(1, null, RANGE_UB_INF)) 236 | }], 237 | ['(,1)', function (t, value) { 238 | t.deepEqual(value, new Range(null, 1, RANGE_LB_INF)) 239 | }], 240 | ['(0,5)', function (t, value) { 241 | t.deepEqual(value, new Range(0, 5, 0)) 242 | }], 243 | ['(,1]', function (t, value) { 244 | t.deepEqual(value, new Range(null, 1, RANGE_LB_INF | RANGE_UB_INC)) 245 | }], 246 | ['[1,)', function (t, value) { 247 | t.deepEqual(value, new Range(1, null, RANGE_LB_INC | RANGE_UB_INF)) 248 | }], 249 | ['[0,5)', function (t, value) { 250 | t.deepEqual(value, new Range(0, 5, RANGE_LB_INC)) 251 | }], 252 | ['(0,5]', function (t, value) { 253 | t.deepEqual(value, new Range(0, 5, RANGE_UB_INC)) 254 | }], 255 | ['[0,5]', function (t, value) { 256 | t.deepEqual(value, new Range(0, 5, RANGE_LB_INC | RANGE_UB_INC)) 257 | }] 258 | ] 259 | } 260 | 261 | exports.int8range = { 262 | format: 'text', 263 | id: 3926, 264 | tests: [ 265 | ['empty', function (t, value) { 266 | t.deepEqual(value, new Range(null, null, RANGE_EMPTY)) 267 | }], 268 | ['(,)', function (t, value) { 269 | t.deepEqual(value, new Range(null, null, RANGE_LB_INF | RANGE_UB_INF)) 270 | }], 271 | ['(1,)', function (t, value) { 272 | t.deepEqual(value, new Range('1', null, RANGE_UB_INF)) 273 | }], 274 | ['(,1)', function (t, value) { 275 | t.deepEqual(value, new Range(null, '1', RANGE_LB_INF)) 276 | }], 277 | ['(0,5)', function (t, value) { 278 | t.deepEqual(value, new Range('0', '5', 0)) 279 | }], 280 | ['(,1]', function (t, value) { 281 | t.deepEqual(value, new Range(null, '1', RANGE_LB_INF | RANGE_UB_INC)) 282 | }], 283 | ['[1,)', function (t, value) { 284 | t.deepEqual(value, new Range('1', null, RANGE_LB_INC | RANGE_UB_INF)) 285 | }], 286 | ['[0,5)', function (t, value) { 287 | t.deepEqual(value, new Range('0', '5', RANGE_LB_INC)) 288 | }], 289 | ['(0,5]', function (t, value) { 290 | t.deepEqual(value, new Range('0', '5', RANGE_UB_INC)) 291 | }], 292 | ['[0,5]', function (t, value) { 293 | t.deepEqual(value, new Range('0', '5', RANGE_LB_INC | RANGE_UB_INC)) 294 | }] 295 | ] 296 | } 297 | 298 | const tsrangeEquals = ([lower, upper]) => { 299 | const timestamp = date => new Date(Date.UTC.apply(Date, date)).toUTCString() 300 | return (t, value) => { 301 | if (lower !== null) { 302 | t.equal(value.lower.toUTCString(), timestamp(lower)) 303 | } 304 | if (upper !== null) { 305 | t.equal(value.upper.toUTCString(), timestamp(upper)) 306 | } 307 | } 308 | } 309 | exports.tstzrange = { 310 | format: 'text', 311 | id: 3910, 312 | tests: [ 313 | ['(2010-10-31 14:54:13.74-05:30,)', tsrangeEquals([[2010, 9, 31, 20, 24, 13, 74], null])], 314 | ['(,2010-10-31 14:54:13.74-05:30)', tsrangeEquals([null, [2010, 9, 31, 20, 24, 13, 74]])], 315 | ['(2010-10-30 10:54:13.74-05:30,2010-10-31 14:54:13.74-05:30)', tsrangeEquals([[2010, 9, 30, 16, 24, 13, 74], [2010, 9, 31, 20, 24, 13, 74]])], 316 | ['("2010-10-31 14:54:13.74-05:30",)', tsrangeEquals([[2010, 9, 31, 20, 24, 13, 74], null])], 317 | ['(,"2010-10-31 14:54:13.74-05:30")', tsrangeEquals([null, [2010, 9, 31, 20, 24, 13, 74]])], 318 | ['("2010-10-30 10:54:13.74-05:30","2010-10-31 14:54:13.74-05:30")', tsrangeEquals([[2010, 9, 30, 16, 24, 13, 74], [2010, 9, 31, 20, 24, 13, 74]])] 319 | ] 320 | } 321 | exports.tsrange = { 322 | format: 'text', 323 | id: 3908, 324 | tests: [ 325 | ['(2010-10-31 14:54:13.74,)', tsrangeEquals([[2010, 9, 31, 14, 54, 13, 74], null])], 326 | ['(2010-10-31 14:54:13.74,infinity)', tsrangeEquals([[2010, 9, 31, 14, 54, 13, 74], null])], 327 | ['(,2010-10-31 14:54:13.74)', tsrangeEquals([null, [2010, 9, 31, 14, 54, 13, 74]])], 328 | ['(-infinity,2010-10-31 14:54:13.74)', tsrangeEquals([null, [2010, 9, 31, 14, 54, 13, 74]])], 329 | ['(2010-10-30 10:54:13.74,2010-10-31 14:54:13.74)', tsrangeEquals([[2010, 9, 30, 10, 54, 13, 74], [2010, 9, 31, 14, 54, 13, 74]])], 330 | ['("2010-10-31 14:54:13.74",)', tsrangeEquals([[2010, 9, 31, 14, 54, 13, 74], null])], 331 | ['("2010-10-31 14:54:13.74",infinity)', tsrangeEquals([[2010, 9, 31, 14, 54, 13, 74], null])], 332 | ['(,"2010-10-31 14:54:13.74")', tsrangeEquals([null, [2010, 9, 31, 14, 54, 13, 74]])], 333 | ['(-infinity,"2010-10-31 14:54:13.74")', tsrangeEquals([null, [2010, 9, 31, 14, 54, 13, 74]])], 334 | ['("2010-10-30 10:54:13.74","2010-10-31 14:54:13.74")', tsrangeEquals([[2010, 9, 30, 10, 54, 13, 74], [2010, 9, 31, 14, 54, 13, 74]])] 335 | ] 336 | } 337 | exports.daterange = { 338 | format: 'text', 339 | id: 3912, 340 | tests: [ 341 | ['(2010-10-31,)', function (t, value) { 342 | t.deepEqual(value, new Range('2010-10-31', null, RANGE_UB_INF)) 343 | }], 344 | ['(,2010-10-31)', function (t, value) { 345 | t.deepEqual(value, new Range(null, '2010-10-31', RANGE_LB_INF)) 346 | }], 347 | ['[2010-10-30,2010-10-31]', function (t, value) { 348 | t.deepEqual(value, new Range('2010-10-30', '2010-10-31', RANGE_LB_INC | RANGE_UB_INC)) 349 | }] 350 | ] 351 | } 352 | 353 | function toPostgresInterval (obj) { 354 | const base = Object.create(PostgresInterval.prototype) 355 | return Object.assign(base, obj) 356 | } 357 | exports.interval = { 358 | format: 'text', 359 | id: 1186, 360 | tests: [ 361 | ['01:02:03', function (t, value) { 362 | t.equal(value.toPostgres(), '3 seconds 2 minutes 1 hours') 363 | t.deepEqual(value, toPostgresInterval({ years: 0, months: 0, days: 0, hours: 1, minutes: 2, seconds: 3, milliseconds: 0 })) 364 | }], 365 | ['01:02:03.456', function (t, value) { 366 | t.deepEqual(value, toPostgresInterval({ years: 0, months: 0, days: 0, hours: 1, minutes: 2, seconds: 3, milliseconds: 456 })) 367 | }], 368 | ['1 year -32 days', function (t, value) { 369 | t.equal(value.toPostgres(), '-32 days 1 years') 370 | t.deepEqual(value, toPostgresInterval({ years: 1, months: 0, days: -32, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 })) 371 | }], 372 | ['1 day -00:00:03', function (t, value) { 373 | t.equal(value.toPostgres(), '-3 seconds 1 days') 374 | t.deepEqual(value, toPostgresInterval({ years: 0, months: 0, days: 1, hours: -0, minutes: -0, seconds: -3, milliseconds: -0 })) 375 | }] 376 | ] 377 | } 378 | 379 | exports.bytea = { 380 | format: 'text', 381 | id: 17, 382 | tests: [ 383 | ['foo\\000\\200\\\\\\377', function (t, value) { 384 | const buffer = Buffer.from([102, 111, 111, 0, 128, 92, 255]) 385 | t.ok(buffer.equals(value)) 386 | }], 387 | ['', function (t, value) { 388 | const buffer = Buffer.from([]) 389 | t.ok(buffer.equals(value)) 390 | }] 391 | ] 392 | } 393 | 394 | exports['array/boolean'] = { 395 | format: 'text', 396 | id: 1000, 397 | tests: [ 398 | ['{true,false}', function (t, value) { 399 | t.deepEqual(value, [true, false]) 400 | }] 401 | ] 402 | } 403 | 404 | exports['array/char'] = { 405 | format: 'text', 406 | id: 1014, 407 | tests: [ 408 | ['{foo,bar}', function (t, value) { 409 | t.deepEqual(value, ['foo', 'bar']) 410 | }] 411 | ] 412 | } 413 | 414 | exports['array/varchar'] = { 415 | format: 'text', 416 | id: 1015, 417 | tests: [ 418 | ['{foo,bar}', function (t, value) { 419 | t.deepEqual(value, ['foo', 'bar']) 420 | }] 421 | ] 422 | } 423 | 424 | exports['array/text'] = { 425 | format: 'text', 426 | id: 1008, 427 | tests: [ 428 | ['{foo}', function (t, value) { 429 | t.deepEqual(value, ['foo']) 430 | }] 431 | ] 432 | } 433 | 434 | exports['array/bytea'] = { 435 | format: 'text', 436 | id: 1001, 437 | tests: [ 438 | ['{"\\\\x00000000"}', function (t, value) { 439 | const buffer = Buffer.from('00000000', 'hex') 440 | t.ok(Array.isArray(value)) 441 | t.equal(value.length, 1) 442 | t.ok(buffer.equals(value[0])) 443 | }], 444 | ['{NULL,"\\\\x4e554c4c"}', function (t, value) { 445 | const buffer = Buffer.from('4e554c4c', 'hex') 446 | t.ok(Array.isArray(value)) 447 | t.equal(value.length, 2) 448 | t.equal(value[0], null) 449 | t.ok(buffer.equals(value[1])) 450 | }] 451 | ] 452 | } 453 | 454 | exports['array/numeric'] = { 455 | format: 'text', 456 | id: 1231, 457 | tests: [ 458 | ['{1.2,3.4}', function (t, value) { 459 | t.deepEqual(value, ['1.2', '3.4']) 460 | }] 461 | ] 462 | } 463 | 464 | exports['array/int2'] = { 465 | format: 'text', 466 | id: 1005, 467 | tests: [ 468 | ['{-32768, -32767, 32766, 32767}', function (t, value) { 469 | t.deepEqual(value, [-32768, -32767, 32766, 32767]) 470 | }] 471 | ] 472 | } 473 | 474 | exports['array/int4'] = { 475 | format: 'text', 476 | id: 1005, 477 | tests: [ 478 | ['{-2147483648, -2147483647, 2147483646, 2147483647}', function (t, value) { 479 | t.deepEqual(value, [-2147483648, -2147483647, 2147483646, 2147483647]) 480 | }] 481 | ] 482 | } 483 | 484 | exports['array/int8'] = { 485 | format: 'text', 486 | id: 1016, 487 | tests: [ 488 | [ 489 | '{-9223372036854775808, -9223372036854775807, 9223372036854775806, 9223372036854775807}', 490 | function (t, value) { 491 | t.deepEqual(value, [ 492 | '-9223372036854775808', 493 | '-9223372036854775807', 494 | '9223372036854775806', 495 | '9223372036854775807' 496 | ]) 497 | } 498 | ] 499 | ] 500 | } 501 | 502 | exports['array/json'] = { 503 | format: 'text', 504 | id: 199, 505 | tests: [ 506 | [ 507 | '{{1,2},{[3],"[4,5]"},{null,NULL}}', 508 | function (t, value) { 509 | t.deepEqual(value, [ 510 | [1, 2], 511 | [[3], [4, 5]], 512 | [null, null] 513 | ]) 514 | } 515 | ] 516 | ] 517 | } 518 | 519 | exports['array/jsonb'] = { 520 | format: 'text', 521 | id: 3807, 522 | tests: exports['array/json'].tests 523 | } 524 | 525 | exports['array/point'] = { 526 | format: 'text', 527 | id: 1017, 528 | tests: [ 529 | ['{"(25.1,50.5)","(10.1,40)"}', function (t, value) { 530 | t.deepEqual(value, [{ x: 25.1, y: 50.5 }, { x: 10.1, y: 40 }]) 531 | }] 532 | ] 533 | } 534 | 535 | exports['array/oid'] = { 536 | format: 'text', 537 | id: 1028, 538 | tests: [ 539 | ['{25864,25860}', function (t, value) { 540 | t.deepEqual(value, [25864, 25860]) 541 | }] 542 | ] 543 | } 544 | 545 | exports['array/float4'] = { 546 | format: 'text', 547 | id: 1021, 548 | tests: [ 549 | ['{1.2, 3.4}', function (t, value) { 550 | t.deepEqual(value, [1.2, 3.4]) 551 | }] 552 | ] 553 | } 554 | 555 | exports['array/float8'] = { 556 | format: 'text', 557 | id: 1022, 558 | tests: [ 559 | ['{-12345678.1234567, 12345678.12345678}', function (t, value) { 560 | t.deepEqual(value, [-12345678.1234567, 12345678.12345678]) 561 | }] 562 | ] 563 | } 564 | 565 | exports['array/date'] = { 566 | format: 'text', 567 | id: 1182, 568 | tests: [ 569 | ['{2014-01-01,2015-12-31}', function (t, value) { 570 | t.deepEqual(value, ['2014-01-01', '2015-12-31']) 571 | }] 572 | ] 573 | } 574 | 575 | exports['array/interval'] = { 576 | format: 'text', 577 | id: 1187, 578 | tests: [ 579 | ['{01:02:03,1 day -00:00:03}', function (t, value) { 580 | const expecteds = [toPostgresInterval({ years: 0, months: 0, days: 0, hours: 1, minutes: 2, seconds: 3, milliseconds: 0 }), 581 | toPostgresInterval({ years: 0, months: 0, days: 1, hours: -0, minutes: -0, seconds: -3, milliseconds: -0 })] 582 | t.equal(value.length, 2) 583 | t.deepEqual(value, expecteds) 584 | }] 585 | ] 586 | } 587 | 588 | exports['array/inet'] = { 589 | format: 'text', 590 | id: 1041, 591 | tests: [ 592 | ['{8.8.8.8}', function (t, value) { 593 | t.deepEqual(value, ['8.8.8.8']) 594 | }], 595 | ['{2001:4860:4860::8888}', function (t, value) { 596 | t.deepEqual(value, ['2001:4860:4860::8888']) 597 | }], 598 | ['{127.0.0.1,fd00:1::40e,1.2.3.4}', function (t, value) { 599 | t.deepEqual(value, ['127.0.0.1', 'fd00:1::40e', '1.2.3.4']) 600 | }] 601 | ] 602 | } 603 | 604 | exports['array/cidr'] = { 605 | format: 'text', 606 | id: 651, 607 | tests: [ 608 | ['{172.16.0.0/12}', function (t, value) { 609 | t.deepEqual(value, ['172.16.0.0/12']) 610 | }], 611 | ['{fe80::/10}', function (t, value) { 612 | t.deepEqual(value, ['fe80::/10']) 613 | }], 614 | ['{10.0.0.0/8,fc00::/7,192.168.0.0/24}', function (t, value) { 615 | t.deepEqual(value, ['10.0.0.0/8', 'fc00::/7', '192.168.0.0/24']) 616 | }] 617 | ] 618 | } 619 | 620 | exports['array/macaddr'] = { 621 | format: 'text', 622 | id: 1040, 623 | tests: [ 624 | ['{08:00:2b:01:02:03,16:10:9f:0d:66:00}', function (t, value) { 625 | t.deepEqual(value, ['08:00:2b:01:02:03', '16:10:9f:0d:66:00']) 626 | }] 627 | ] 628 | } 629 | 630 | exports['array/numrange'] = { 631 | format: 'text', 632 | id: 3907, 633 | tests: [ 634 | ['{"[1,2]","(4.5,8)","[10,40)","(-21.2,60.3]"}', function (t, value) { 635 | t.deepEqual(value, ['[1,2]', '(4.5,8)', '[10,40)', '(-21.2,60.3]']) 636 | }], 637 | ['{"[,20]","[3,]","[,]","(,35)","(1,)","(,)"}', function (t, value) { 638 | t.deepEqual(value, ['[,20]', '[3,]', '[,]', '(,35)', '(1,)', '(,)']) 639 | }], 640 | ['{"[,20)","[3,)","[,)","[,35)","[1,)","[,)"}', function (t, value) { 641 | t.deepEqual(value, ['[,20)', '[3,)', '[,)', '[,35)', '[1,)', '[,)']) 642 | }] 643 | ] 644 | } 645 | 646 | exports['binary-string/varchar'] = { 647 | format: 'binary', 648 | id: 1043, 649 | tests: [ 650 | ['bang', 'bang'] 651 | ] 652 | } 653 | 654 | exports['binary-integer/int4'] = { 655 | format: 'binary', 656 | id: 23, 657 | tests: [ 658 | [[0, 0, 0, 100], 100] 659 | ] 660 | } 661 | 662 | exports['binary-smallint/int2'] = { 663 | format: 'binary', 664 | id: 21, 665 | tests: [ 666 | [[0, 101], 101] 667 | ] 668 | } 669 | 670 | exports['binary-bigint/int8'] = { 671 | format: 'binary', 672 | id: 20, 673 | tests: [ 674 | [Buffer.from([0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), '9223372036854775807'] 675 | ] 676 | } 677 | 678 | exports['binary-oid'] = { 679 | format: 'binary', 680 | id: 26, 681 | tests: [ 682 | [[0, 0, 0, 103], 103] 683 | ] 684 | } 685 | 686 | exports['binary-numeric'] = { 687 | format: 'binary', 688 | id: 1700, 689 | tests: [ 690 | [ 691 | [0, 2, 0, 0, 0, 0, 0, 0x64, 0, 12, 0xd, 0x48], 692 | '12.3400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' 693 | ] 694 | ] 695 | } 696 | 697 | exports['binary-real/float4'] = { 698 | format: 'binary', 699 | id: 700, 700 | tests: [ 701 | [[0x41, 0x48, 0x00, 0x00], 12.5] 702 | ] 703 | } 704 | 705 | exports['binary-boolean'] = { 706 | format: 'binary', 707 | id: 16, 708 | tests: [ 709 | [[1], true], 710 | [[0], false] 711 | ] 712 | } 713 | 714 | exports['binary-string'] = { 715 | format: 'binary', 716 | id: 25, 717 | tests: [ 718 | [ 719 | Buffer.from([0x73, 0x6c, 0x61, 0x64, 0x64, 0x61]), 720 | 'sladda' 721 | ] 722 | ] 723 | } 724 | 725 | exports['binary-array/int4'] = { 726 | format: 'binary', 727 | id: 1007, 728 | tests: [ 729 | [ 730 | Buffer.from([ 731 | 0, 0, 0, 1, 732 | 0, 0, 0, 0, 733 | 0, 0, 0, 0x17, // int4[] 734 | 0, 0, 0, 1, 735 | 0, 0, 0, 1, 736 | 0, 0, 0, 4, 0xff, 0xff, 0xff, 0xff 737 | ]), 738 | function (t, value) { 739 | t.deepEqual(value, [-1]) 740 | } 741 | ] 742 | ] 743 | } 744 | 745 | exports['binary-array/int8'] = { 746 | format: 'binary', 747 | id: 1016, 748 | tests: [ 749 | [ 750 | Buffer.from([ 751 | 0, 0, 0, 1, 752 | 0, 0, 0, 0, 753 | 0, 0, 0, 0x14, // int8[] 754 | 0, 0, 0, 1, 755 | 0, 0, 0, 1, 756 | 0, 0, 0, 8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 757 | ]), 758 | function (t, value) { 759 | t.deepEqual(value, ['-1']) 760 | } 761 | ], 762 | [ 763 | Buffer.from([ 764 | 0, 0, 0, 1, 765 | 0, 0, 0, 0, 766 | 0, 0, 0, 0x14, // int8[] 767 | 0, 0, 0, 1, 768 | 0, 0, 0, 1, 769 | 0, 0, 0, 8, 0x01, 0xb6, 0x9b, 0x4b, 0xac, 0xd0, 0x5f, 0x15 770 | ]), 771 | function (t, value) { 772 | t.deepEqual(value, ['123456789123456789']) 773 | } 774 | ] 775 | ] 776 | } 777 | 778 | exports.point = { 779 | format: 'text', 780 | id: 600, 781 | tests: [ 782 | ['(25.1,50.5)', function (t, value) { 783 | t.deepEqual(value, { x: 25.1, y: 50.5 }) 784 | }] 785 | ] 786 | } 787 | 788 | exports.circle = { 789 | format: 'text', 790 | id: 718, 791 | tests: [ 792 | ['<(25,10),5>', function (t, value) { 793 | t.deepEqual(value, { x: 25, y: 10, radius: 5 }) 794 | }] 795 | ] 796 | } 797 | 798 | function dateEquals () { 799 | const timestamp = Date.UTC.apply(Date, arguments) 800 | return function (t, value) { 801 | t.equal(value.toUTCString(), new Date(timestamp).toUTCString()) 802 | } 803 | } 804 | --------------------------------------------------------------------------------