@@ -12,7 +12,15 @@ import { getLogger } from "@logtape/logtape";
1212const logger = getLogger ( [ "fedify" , "pglite" , "kv" ] ) ;
1313
1414function quoteIdentifier ( identifier : string ) : string {
15- return `"${ identifier . replaceAll ( '"' , '""' ) . replaceAll ( "." , '"."' ) } "` ;
15+ const parts = identifier . split ( "." ) ;
16+ if ( parts . includes ( "" ) ) {
17+ throw new TypeError (
18+ `Invalid table name for the key–value store: ${
19+ JSON . stringify ( identifier )
20+ } `,
21+ ) ;
22+ }
23+ return parts . map ( ( part ) => `"${ part . replaceAll ( '"' , '""' ) } "` ) . join ( "." ) ;
1624}
1725
1826function serializeJson ( value : unknown ) : string {
@@ -29,6 +37,8 @@ function serializeJson(value: unknown): string {
2937export interface PgliteKvStoreOptions {
3038 /**
3139 * The table name to use for the key–value store.
40+ * A `.` separates the schema from the table name, so neither the whole
41+ * name nor any segment between dots can be empty.
3242 * `"fedify_kv_v2"` by default.
3343 * @default `"fedify_kv_v2"`
3444 */
@@ -68,6 +78,7 @@ export interface PgliteKvStoreOptions {
6878export class PgliteKvStore implements KvStore {
6979 readonly #pg: PGliteInterface ;
7080 readonly #tableName: string ;
81+ readonly #quotedTableName: string ;
7182 #initialized: boolean ;
7283 #initializing?: Promise < void > ;
7384
@@ -82,12 +93,13 @@ export class PgliteKvStore implements KvStore {
8293 ) {
8394 this . #pg = pg ;
8495 this . #tableName = options . tableName ?? "fedify_kv_v2" ;
96+ this . #quotedTableName = quoteIdentifier ( this . #tableName) ;
8597 this . #initialized = options . initialized ?? false ;
8698 }
8799
88100 async #expire( ) : Promise < void > {
89101 await this . #pg. query ( `
90- DELETE FROM ${ quoteIdentifier ( this . #tableName ) }
102+ DELETE FROM ${ this . #quotedTableName }
91103 WHERE ttl IS NOT NULL AND created + ttl < CURRENT_TIMESTAMP;
92104 ` ) ;
93105 }
@@ -97,7 +109,7 @@ export class PgliteKvStore implements KvStore {
97109 const result = await this . #pg. query < { value : T } > (
98110 `
99111 SELECT value
100- FROM ${ quoteIdentifier ( this . #tableName ) }
112+ FROM ${ this . #quotedTableName }
101113 WHERE key = $1::text[]
102114 AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP);
103115 ` ,
@@ -117,7 +129,7 @@ export class PgliteKvStore implements KvStore {
117129 : Temporal . Duration . from ( options . ttl ) . toString ( ) ;
118130 await this . #pg. query (
119131 `
120- INSERT INTO ${ quoteIdentifier ( this . #tableName ) } (key, value, ttl)
132+ INSERT INTO ${ this . #quotedTableName } (key, value, ttl)
121133 VALUES ($1::text[], $2::text::jsonb, $3::text::interval)
122134 ON CONFLICT (key)
123135 DO UPDATE SET
@@ -134,7 +146,7 @@ export class PgliteKvStore implements KvStore {
134146 await this . initialize ( ) ;
135147 await this . #pg. query (
136148 `
137- DELETE FROM ${ quoteIdentifier ( this . #tableName ) }
149+ DELETE FROM ${ this . #quotedTableName }
138150 WHERE key = $1::text[];
139151 ` ,
140152 [ key ] ,
@@ -164,7 +176,7 @@ export class PgliteKvStore implements KvStore {
164176 if ( expectedValue === undefined ) {
165177 const result = await this . #pg. query (
166178 `
167- INSERT INTO ${ quoteIdentifier ( this . #tableName ) } AS existing
179+ INSERT INTO ${ this . #quotedTableName } AS existing
168180 (key, value, created, ttl)
169181 VALUES (
170182 $1::text[],
@@ -187,7 +199,7 @@ export class PgliteKvStore implements KvStore {
187199 } else if ( newValue === undefined ) {
188200 const result = await this . #pg. query (
189201 `
190- DELETE FROM ${ quoteIdentifier ( this . #tableName ) }
202+ DELETE FROM ${ this . #quotedTableName }
191203 WHERE key = $1::text[]
192204 AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
193205 AND value = $2::text::jsonb
@@ -199,7 +211,7 @@ export class PgliteKvStore implements KvStore {
199211 } else {
200212 const result = await this . #pg. query (
201213 `
202- UPDATE ${ quoteIdentifier ( this . #tableName ) }
214+ UPDATE ${ this . #quotedTableName }
203215 SET
204216 value = $3::text::jsonb,
205217 created = CURRENT_TIMESTAMP,
@@ -230,14 +242,14 @@ export class PgliteKvStore implements KvStore {
230242 const result = prefix == null || prefix . length === 0
231243 ? await this . #pg. query < { key : KvKey ; value : unknown } > ( `
232244 SELECT key, value
233- FROM ${ quoteIdentifier ( this . #tableName ) }
245+ FROM ${ this . #quotedTableName }
234246 WHERE ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP
235247 ORDER BY key;
236248 ` )
237249 : await this . #pg. query < { key : KvKey ; value : unknown } > (
238250 `
239251 SELECT key, value
240- FROM ${ quoteIdentifier ( this . #tableName ) }
252+ FROM ${ this . #quotedTableName }
241253 WHERE array_length(key, 1) >= $1
242254 AND key[1:$1] = $2::text[]
243255 AND (ttl IS NULL OR created + ttl > CURRENT_TIMESTAMP)
@@ -260,7 +272,7 @@ export class PgliteKvStore implements KvStore {
260272 tableName : this . #tableName,
261273 } ) ;
262274 await this . #pg. query ( `
263- CREATE TABLE IF NOT EXISTS ${ quoteIdentifier ( this . #tableName ) } (
275+ CREATE TABLE IF NOT EXISTS ${ this . #quotedTableName } (
264276 key text[] PRIMARY KEY,
265277 value jsonb NOT NULL,
266278 created timestamp with time zone DEFAULT CURRENT_TIMESTAMP,
@@ -286,7 +298,7 @@ export class PgliteKvStore implements KvStore {
286298 async drop ( ) : Promise < void > {
287299 await this . #pg. waitReady ;
288300 await this . #pg. query (
289- `DROP TABLE IF EXISTS ${ quoteIdentifier ( this . #tableName ) } ;` ,
301+ `DROP TABLE IF EXISTS ${ this . #quotedTableName } ;` ,
290302 ) ;
291303 this . #initialized = false ;
292304 this . #initializing = undefined ;
0 commit comments