1- import { DataSource , Source } from "../types" ;
2- const parser = new ( require ( 'node-sql-parser' ) . Parser ) ( ) ;
1+ import { StarbaseDBConfiguration } from "../handler" ;
2+ import { DataSource , QueryResult } from "../types" ;
3+ import sqlparser from "node-sql-parser" ;
4+ const parser = new sqlparser . Parser ( ) ;
35
46function hasModifyingStatement ( ast : any ) : boolean {
5- // Check if current node is a modifying statement
6- if ( ast . type && [ 'insert' , 'update' , 'delete' ] . includes ( ast . type . toLowerCase ( ) ) ) {
7- return true ;
8- }
7+ // Check if current node is a modifying statement
8+ if (
9+ ast . type &&
10+ [ "insert" , "update" , "delete" ] . includes ( ast . type . toLowerCase ( ) )
11+ ) {
12+ return true ;
13+ }
914
10- // Recursively check all properties of the AST
11- for ( const key in ast ) {
12- if ( typeof ast [ key ] === 'object' && ast [ key ] !== null ) {
13- if ( Array . isArray ( ast [ key ] ) ) {
14- if ( ast [ key ] . some ( item => hasModifyingStatement ( item ) ) ) {
15- return true ;
16- }
17- } else if ( hasModifyingStatement ( ast [ key ] ) ) {
18- return true ;
19- }
15+ // Recursively check all properties of the AST
16+ for ( const key in ast ) {
17+ if ( typeof ast [ key ] === "object" && ast [ key ] !== null ) {
18+ if ( Array . isArray ( ast [ key ] ) ) {
19+ if ( ast [ key ] . some ( ( item ) => hasModifyingStatement ( item ) ) ) {
20+ return true ;
2021 }
22+ } else if ( hasModifyingStatement ( ast [ key ] ) ) {
23+ return true ;
24+ }
2125 }
26+ }
2227
23- return false ;
28+ return false ;
2429}
2530
26- export async function beforeQueryCache ( sql : string , params ?: any [ ] , dataSource ?: DataSource , dialect ?: string ) : Promise < any | null > {
27- // Currently we do not support caching queries that have dynamic parameters
28- if ( params ?. length ) return null
29- if ( dataSource ?. source === Source . internal || ! dataSource ?. request . headers . has ( 'X-Starbase-Cache' ) ) return null
31+ export async function beforeQueryCache ( opts : {
32+ sql : string ;
33+ params ?: unknown [ ] ;
34+ dataSource : DataSource ;
35+ } ) : Promise < unknown | null > {
36+ const { sql, params = [ ] , dataSource } = opts ;
3037
31- if ( ! dialect ) dialect = 'sqlite'
32- if ( dialect . toLowerCase ( ) === 'postgres' ) dialect = 'postgresql'
38+ // Currently we do not support caching queries that have dynamic parameters
39+ if ( params . length ) {
40+ return null ;
41+ }
3342
34- let ast = parser . astify ( sql , { database : dialect } ) ;
35- if ( hasModifyingStatement ( ast ) ) return null
36-
37- const fetchCacheStatement = `SELECT timestamp, ttl, query, results FROM tmp_cache WHERE query = ?`
38- const result = await dataSource . internalConnection ?. durableObject . executeQuery ( fetchCacheStatement , [ sql ] , false ) as any [ ] ;
39-
40- if ( result ?. length ) {
41- const { timestamp, ttl, results } = result [ 0 ] ;
42- const expirationTime = new Date ( timestamp ) . getTime ( ) + ( ttl * 1000 ) ;
43-
44- if ( Date . now ( ) < expirationTime ) {
45- return JSON . parse ( results )
46- }
43+ // If it's an internal request, or cache is not enabled, return null.
44+ if ( dataSource . source === "internal" || ! dataSource . cache ) {
45+ return null ;
46+ }
47+
48+ const dialect =
49+ dataSource . source === "external" ? dataSource . external ! . dialect : "sqlite" ;
50+
51+ let ast = parser . astify ( sql , { database : dialect } ) ;
52+ if ( hasModifyingStatement ( ast ) ) return null ;
53+
54+ const fetchCacheStatement = `SELECT timestamp, ttl, query, results FROM tmp_cache WHERE query = ?` ;
55+
56+ type QueryResult = {
57+ timestamp : string ;
58+ ttl : number ;
59+ results : string ;
60+ } ;
61+
62+ const result = await dataSource . rpc . executeQuery ( {
63+ sql : fetchCacheStatement ,
64+ params : [ sql ] ,
65+ } ) as any [ ] ;
66+
67+ if ( result ?. length ) {
68+ const { timestamp, ttl, results } = result [ 0 ] as QueryResult ;
69+ const expirationTime = new Date ( timestamp ) . getTime ( ) + ttl * 1000 ;
70+
71+ if ( Date . now ( ) < expirationTime ) {
72+ return JSON . parse ( results ) ;
4773 }
74+ }
4875
49- return null
76+ return null ;
5077}
5178
5279// Serialized RPC arguemnts are limited to 1MiB in size at the moment for Cloudflare
@@ -55,36 +82,57 @@ export async function beforeQueryCache(sql: string, params?: any[], dataSource?:
5582// to look into include using Cloudflare Cache but need to find a good way to cache the
5683// response in a safe way for our use case. Another option is another service for queues
5784// or another way to ingest it directly to the Durable Object.
58- export async function afterQueryCache ( sql : string , params : any [ ] | undefined , result : any , dataSource ?: DataSource , dialect ?: string ) {
59- // Currently we do not support caching queries that have dynamic parameters
60- if ( params ?. length ) return ;
61- if ( dataSource ?. source === Source . internal || ! dataSource ?. request . headers . has ( 'X-Starbase-Cache' ) ) return null
62-
63- try {
64- if ( ! dialect ) dialect = 'sqlite'
65- if ( dialect . toLowerCase ( ) === 'postgres' ) dialect = 'postgresql'
66-
67- let ast = parser . astify ( sql , { database : dialect } ) ;
68-
69- // If any modifying query exists within our SQL statement then we shouldn't proceed
70- if ( hasModifyingStatement ( ast ) ) return ;
71-
72- const timestamp = Date . now ( ) ;
73- const results = JSON . stringify ( result ) ;
74-
75- const exists = await dataSource . internalConnection ?. durableObject . executeQuery (
76- 'SELECT 1 FROM tmp_cache WHERE query = ? LIMIT 1' ,
77- [ sql ] ,
78- false
79- ) as any [ ] ;
80-
81- const query = exists ?. length
82- ? { sql : 'UPDATE tmp_cache SET timestamp = ?, results = ? WHERE query = ?' , params : [ timestamp , results , sql ] }
83- : { sql : 'INSERT INTO tmp_cache (timestamp, ttl, query, results) VALUES (?, ?, ?, ?)' , params : [ timestamp , 60 , sql , results ] } ;
84-
85- await dataSource . internalConnection ?. durableObject . executeQuery ( query . sql , query . params , false ) ;
86- } catch ( error ) {
87- console . error ( 'Error in cache operation:' , error ) ;
88- return ;
89- }
90- }
85+ export async function afterQueryCache ( opts : {
86+ sql : string ;
87+ params : unknown [ ] | undefined ;
88+ result : unknown ;
89+ dataSource : DataSource ;
90+ } ) {
91+ const { sql, params, result, dataSource } = opts ;
92+
93+ // Currently we do not support caching queries that have dynamic parameters
94+ if ( params ?. length ) return ;
95+
96+ // If it's an internal request, or cache is not enabled, return null.
97+ if ( dataSource . source === "internal" || ! dataSource . cache ) {
98+ return null ;
99+ }
100+
101+ try {
102+ const dialect =
103+ dataSource . source === "external"
104+ ? dataSource . external ! . dialect
105+ : "sqlite" ;
106+
107+ let ast = parser . astify ( sql , { database : dialect } ) ;
108+
109+ // If any modifying query exists within our SQL statement then we shouldn't proceed
110+ if ( hasModifyingStatement ( ast ) ) return ;
111+
112+ const timestamp = Date . now ( ) ;
113+ const results = JSON . stringify ( result ) ;
114+
115+ const exists = await dataSource . rpc . executeQuery ( {
116+ sql : "SELECT 1 FROM tmp_cache WHERE query = ? LIMIT 1" ,
117+ params : [ sql ] ,
118+ } ) as QueryResult [ ] ;
119+
120+ const query = exists ?. length
121+ ? {
122+ sql : "UPDATE tmp_cache SET timestamp = ?, results = ? WHERE query = ?" ,
123+ params : [ timestamp , results , sql ] ,
124+ }
125+ : {
126+ sql : "INSERT INTO tmp_cache (timestamp, ttl, query, results) VALUES (?, ?, ?, ?)" ,
127+ params : [ timestamp , dataSource . cacheTTL ?? 60 , sql , results ] ,
128+ } ;
129+
130+ await dataSource . rpc . executeQuery ( {
131+ sql : query . sql ,
132+ params : query . params ,
133+ } ) ;
134+ } catch ( error ) {
135+ console . error ( "Error in cache operation:" , error ) ;
136+ return ;
137+ }
138+ }
0 commit comments