1+ import * as spinPg from 'spin:postgres/[email protected] ' ; 2+
3+ export type SpinPostgresV3ValueBoolean = { tag : 'boolean' ; val : boolean } ;
4+ export type SpinPostgresV3ValueInt8 = { tag : 'int8' ; val : number } ;
5+ export type SpinPostgresV3ValueInt16 = { tag : 'int16' ; val : number } ;
6+ export type SpinPostgresV3ValueInt32 = { tag : 'int32' ; val : number } ;
7+ export type SpinPostgresV3ValueInt64 = { tag : 'int64' ; val : bigint } ;
8+ export type SpinPostgresV3ValueFloating32 = { tag : 'floating32' ; val : number } ;
9+ export type SpinPostgresV3ValueFloating64 = { tag : 'floating64' ; val : number } ;
10+ export type SpinPostgresV3ValueStr = { tag : 'str' ; val : string } ;
11+ export type SpinPostgresV3ValueBinary = { tag : 'binary' ; val : Uint8Array } ;
12+ export type SpinPostgresV3ValueDate = {
13+ tag : 'date' ;
14+ val : [ number , number , number ] ;
15+ } ;
16+ export type SpinPostgresV3ValueTime = {
17+ tag : 'time' ;
18+ val : [ number , number , number , number ] ;
19+ } ;
20+ export type SpinPostgresV3ValueDateTime = {
21+ tag : 'datetime' ;
22+ val : [ number , number , number , number , number , number , number ] ;
23+ } ;
24+ export type SpinPostgresV3ValueTimeStamp = { tag : 'timestamp' ; val : bigint } ;
25+ export type SpinPostgresV3ValueDbNull = { tag : 'db-null' } ;
26+
27+ export type SpinPostgresV3ParameterValue =
28+ | SpinPostgresV3ValueBoolean
29+ | SpinPostgresV3ValueInt8
30+ | SpinPostgresV3ValueInt16
31+ | SpinPostgresV3ValueInt32
32+ | SpinPostgresV3ValueInt64
33+ | SpinPostgresV3ValueFloating32
34+ | SpinPostgresV3ValueFloating64
35+ | SpinPostgresV3ValueStr
36+ | SpinPostgresV3ValueBinary
37+ | SpinPostgresV3ValueDate
38+ | SpinPostgresV3ValueTime
39+ | SpinPostgresV3ValueDateTime
40+ | SpinPostgresV3ValueTimeStamp
41+ | SpinPostgresV3ValueDbNull ;
42+
43+ export type PostgresV3ParameterValue =
44+ | SpinPostgresV3ParameterValue
45+ | number
46+ | bigint
47+ | boolean
48+ | null
49+ | Uint8Array
50+ | string ;
51+
52+ export enum PostgresV3DataType {
53+ PostgresV3Boolean = 'boolean' ,
54+ PostgresV3Int8 = 'int8' ,
55+ PostgresV3Int16 = 'int16' ,
56+ PostgresV3Int32 = 'int32' ,
57+ PostgresV3Int64 = 'int64' ,
58+ PostgresV3Floating32 = 'floating32' ,
59+ PostgresV3Floating64 = 'floating64' ,
60+ PostgresV3Str = 'str' ,
61+ PostgresV3Binary = 'binary' ,
62+ PostgresV3Date = 'date' ,
63+ PostgresV3Time = 'time' ,
64+ PostgresV3DateTime = 'datetime' ,
65+ PostgresV3TimeStamp = 'timestamp' ,
66+ PostgresV3Other = 'other' ,
67+ }
68+
69+ export interface PostgresV3Column {
70+ name : string ;
71+ dataType : PostgresV3DataType ;
72+ }
73+
74+ export interface PostgresV3RowSet {
75+ columns : PostgresV3Column [ ] ;
76+ rows : {
77+ [ key : string ] :
78+ | number
79+ | boolean
80+ | bigint
81+ | null
82+ | string
83+ | Uint8Array
84+ | Date ;
85+ } [ ] ;
86+ }
87+
88+ export type PostgresV3DbBoolean = { tag : 'boolean' ; val : boolean } ;
89+ export type PostgresV3DbInt8 = { tag : 'int8' ; val : number } ;
90+ export type PostgresV3DbInt16 = { tag : 'int16' ; val : number } ;
91+ export type PostgresV3DbInt32 = { tag : 'int32' ; val : number } ;
92+ export type PostgresV3DbInt64 = { tag : 'int64' ; val : number } ;
93+ export type PostgresV3DbFloating32 = { tag : 'floating32' ; val : number } ;
94+ export type PostgresV3DbFloating64 = { tag : 'floating64' ; val : number } ;
95+ export type PostgresV3DbStr = { tag : 'str' ; val : string } ;
96+ export type PostgresV3DbBinary = { tag : 'binary' ; val : Uint8Array } ;
97+ export type PostgresV3DbDate = { tag : 'date' ; val : [ number , number , number ] } ;
98+ export type PostgresV3DbTime = {
99+ tag : 'time' ;
100+ val : [ number , number , number , number ] ;
101+ } ;
102+ export type PostgresV3DbDateTime = {
103+ tag : 'datetime' ;
104+ val : [ number , number , number , number , number , number , number ] ;
105+ } ;
106+ export type PostgresV3DbTimeastamp = { tag : 'timestamp' ; val : bigint } ;
107+ export type PostgresV3DbNull = { tag : 'db-null' } ;
108+ export type PostgresV3DbUnsupported = { tag : 'unsupported' } ;
109+
110+ export type RdbmsDbValue =
111+ | PostgresV3DbBoolean
112+ | PostgresV3DbInt8
113+ | PostgresV3DbInt16
114+ | PostgresV3DbInt32
115+ | PostgresV3DbInt64
116+ | PostgresV3DbFloating32
117+ | PostgresV3DbFloating64
118+ | PostgresV3DbStr
119+ | PostgresV3DbBinary
120+ | PostgresV3DbDate
121+ | PostgresV3DbTime
122+ | PostgresV3DbDateTime
123+ | PostgresV3DbTimeastamp
124+ | PostgresV3DbNull
125+ | PostgresV3DbUnsupported ;
126+
127+ export type RdbmsRow = RdbmsDbValue [ ] ;
128+
129+ export interface SpinRdbmsRowSet {
130+ columns : PostgresV3Column [ ] ;
131+ rows : RdbmsRow [ ] ;
132+ }
133+
134+ /**
135+ * Interface representing a PostgreSQL connection with methods for querying and executing statements.
136+ * @interface PostgresConnection
137+ */
138+ export interface PostgresConnection {
139+ /**
140+ * Queries the database with the specified statement and parameters.
141+ * @param {string } statement - The SQL statement to execute.
142+ * @param {PostgresV3Value[] } params - The parameters for the SQL statement.
143+ * @returns {RdbmsRowSet } The result set of the query.
144+ */
145+ query : (
146+ statement : string ,
147+ params : PostgresV3ParameterValue [ ] ,
148+ ) => PostgresV3RowSet ;
149+ /**
150+ * Executes a statement on the database with the specified parameters.
151+ * @param {string } statement - The SQL statement to execute.
152+ * @param {PostgresV3Value[] } params - The parameters for the SQL statement.
153+ * @returns {number } The number of rows affected by the execution.
154+ */
155+ execute : ( statement : string , params : PostgresV3ParameterValue [ ] ) => bigint ;
156+ }
157+
158+ function createPostgresConnection (
159+ connection : spinPg . Connection ,
160+ ) : PostgresConnection {
161+ return {
162+ query : ( statement : string , params : PostgresV3ParameterValue [ ] ) => {
163+ let santizedParams = convertRdbmsToWitTypes ( params ) ;
164+ let ret = connection . query ( statement , santizedParams ) as SpinRdbmsRowSet ;
165+ let results : PostgresV3RowSet = {
166+ columns : ret . columns ,
167+ rows : [ ] ,
168+ } ;
169+ ret . rows . map ( ( k : RdbmsRow , rowIndex : number ) => {
170+ results . rows . push ( { } ) ;
171+ k . map ( ( val , valIndex : number ) => {
172+ switch ( val . tag ) {
173+ case 'date' : {
174+ // Date (year, month, day)
175+ const [ year , month , day ] = val . val ;
176+ results . rows [ rowIndex ] [ results . columns [ valIndex ] . name ] = new Date (
177+ Date . UTC ( year , month - 1 , day ) ,
178+ ) ; // UTC Date object
179+ break ;
180+ }
181+
182+ case 'time' : {
183+ // Time (hour, minute, second, nanosecond)
184+ const [ hour , minute , second , nanosecond ] = val . val ;
185+ const date = new Date ( Date . UTC ( 1970 , 0 , 1 , hour , minute , second ) ) ; // Using an arbitrary date
186+ date . setMilliseconds ( nanosecond / 1_000_000 ) ; // Convert nanoseconds to milliseconds
187+ results . rows [ rowIndex ] [ results . columns [ valIndex ] . name ] = date ; // UTC Date object with only time set
188+ break ;
189+ }
190+
191+ case 'datetime' : {
192+ // DateTime (year, month, day, hour, minute, second, nanosecond)
193+ const [ year , month , day , hour , minute , second , nanosecond ] =
194+ val . val ;
195+ const date = new Date (
196+ Date . UTC ( year , month - 1 , day , hour , minute , second ) ,
197+ ) ;
198+ date . setMilliseconds ( nanosecond / 1_000_000 ) ; // Convert nanoseconds to milliseconds
199+ results . rows [ rowIndex ] [ results . columns [ valIndex ] . name ] = date ; // Complete UTC Date object
200+ break ;
201+ }
202+
203+ case 'timestamp' : {
204+ // Timestamp (seconds since epoch)
205+ const seconds = val . val as unknown as number ;
206+ results . rows [ rowIndex ] [ results . columns [ valIndex ] . name ] = new Date (
207+ seconds * 1000 ,
208+ ) ; // Convert seconds to milliseconds
209+ break ;
210+ }
211+
212+ default : {
213+ results . rows [ rowIndex ] [ results . columns [ valIndex ] . name ] =
214+ val . tag == 'db-null' || val . tag == 'unsupported'
215+ ? null
216+ : val . val ;
217+ break ;
218+ }
219+ }
220+ } ) ;
221+ } ) ;
222+ return results ;
223+ } ,
224+ execute : ( statement : string , params : PostgresV3ParameterValue [ ] ) => {
225+ let santizedParams = convertRdbmsToWitTypes ( params ) ;
226+ let ret = connection . execute ( statement , santizedParams ) as bigint ;
227+ return ret ;
228+ } ,
229+ } ;
230+ }
231+
232+ /**
233+ * Opens a PostgreSQL connection to the specified address.
234+ * @param {string } address - The address of the PostgreSQL server.
235+ * @returns {PostgresConnection } The PostgreSQL connection object.
236+ */
237+ export function open ( address : string ) : PostgresConnection {
238+ return createPostgresConnection ( spinPg . Connection . open ( address ) ) ;
239+ }
240+
241+ function convertRdbmsToWitTypes (
242+ parameters : PostgresV3ParameterValue [ ] ,
243+ ) : SpinPostgresV3ParameterValue [ ] {
244+ let sanitized : SpinPostgresV3ParameterValue [ ] = [ ] ;
245+ for ( let k of parameters ) {
246+ if ( typeof k === 'object' ) {
247+ sanitized . push ( k as SpinPostgresV3ParameterValue ) ;
248+ continue ;
249+ }
250+ if ( typeof k === 'string' ) {
251+ sanitized . push ( { tag : 'str' , val : k } ) ;
252+ continue ;
253+ }
254+ if ( typeof k === null ) {
255+ sanitized . push ( { tag : 'db-null' } ) ;
256+ continue ;
257+ }
258+ if ( typeof k === 'boolean' ) {
259+ sanitized . push ( { tag : 'boolean' , val : k } ) ;
260+ continue ;
261+ }
262+ if ( typeof k === 'bigint' ) {
263+ sanitized . push ( { tag : 'int64' , val : k } ) ;
264+ continue ;
265+ }
266+ if ( typeof k === 'number' ) {
267+ isFloat ( k )
268+ ? sanitized . push ( { tag : 'floating64' , val : k } )
269+ : sanitized . push ( { tag : 'int32' , val : k } ) ;
270+ continue ;
271+ }
272+ if ( ( k as any ) instanceof Uint8Array ) {
273+ sanitized . push ( { tag : 'binary' , val : k } ) ;
274+ continue ;
275+ }
276+ }
277+ return sanitized ;
278+ }
279+
280+ function isFloat ( number : number ) {
281+ return number % 1 !== 0 ;
282+ }
0 commit comments