@risingwave/wavelet-server 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ddl-manager.ts +102 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@risingwave/wavelet-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Wavelet server - WebSocket fanout layer for RisingWave",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"pg": "^8.13.0",
|
|
14
14
|
"ws": "^8.18.0",
|
|
15
15
|
"jose": "^6.0.0",
|
|
16
|
-
"@risingwave/wavelet": "0.1.
|
|
16
|
+
"@risingwave/wavelet": "0.1.4"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/pg": "^8.11.0",
|
package/src/ddl-manager.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import pg from 'pg'
|
|
2
|
-
import type { WaveletConfig, StreamDef, ViewDef, SqlFragment } from '@risingwave/wavelet'
|
|
2
|
+
import type { WaveletConfig, StreamDef, ViewDef, SqlFragment, PostgresCdcSource } from '@risingwave/wavelet'
|
|
3
3
|
|
|
4
4
|
const { Client } = pg
|
|
5
5
|
|
|
6
6
|
export interface DdlAction {
|
|
7
7
|
type: 'create' | 'update' | 'delete' | 'unchanged'
|
|
8
|
-
resource: 'stream' | 'view' | 'subscription'
|
|
8
|
+
resource: 'stream' | 'source' | 'view' | 'subscription'
|
|
9
9
|
name: string
|
|
10
10
|
detail?: string
|
|
11
11
|
}
|
|
@@ -75,6 +75,7 @@ export class DdlManager {
|
|
|
75
75
|
const existingSubscriptions = await this.getExistingSubscriptions()
|
|
76
76
|
|
|
77
77
|
const desiredStreams = config.streams ?? {}
|
|
78
|
+
const desiredSources = config.sources ?? {}
|
|
78
79
|
const desiredViews = config.views ?? {}
|
|
79
80
|
|
|
80
81
|
// 2. Determine which streams (tables) to create or remove
|
|
@@ -91,6 +92,22 @@ export class DdlManager {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
// 3b. Sync CDC sources - create Postgres CDC tables
|
|
96
|
+
const existingSources = await this.getExistingSources()
|
|
97
|
+
for (const [sourceName, sourceDef] of Object.entries(desiredSources)) {
|
|
98
|
+
if (sourceDef.type === 'postgres') {
|
|
99
|
+
for (const tableName of sourceDef.tables) {
|
|
100
|
+
const cdcTableName = `${sourceName}_${tableName}`
|
|
101
|
+
if (existingSources.has(cdcTableName) || existingTables.has(cdcTableName)) {
|
|
102
|
+
actions.push({ type: 'unchanged', resource: 'source', name: cdcTableName })
|
|
103
|
+
} else {
|
|
104
|
+
await this.createCdcSource(sourceName, tableName, sourceDef)
|
|
105
|
+
actions.push({ type: 'create', resource: 'source', name: cdcTableName })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
94
111
|
// 4. Sync views - create, update, or leave unchanged
|
|
95
112
|
for (const [viewName, viewDef] of Object.entries(desiredViews)) {
|
|
96
113
|
const subName = `wavelet_sub_${viewName}`
|
|
@@ -207,6 +224,18 @@ export class DdlManager {
|
|
|
207
224
|
return views
|
|
208
225
|
}
|
|
209
226
|
|
|
227
|
+
private async getExistingSources(): Promise<Set<string>> {
|
|
228
|
+
try {
|
|
229
|
+
const result = await this.client!.query(
|
|
230
|
+
`SELECT name FROM rw_catalog.rw_tables WHERE schema_id = (SELECT id FROM rw_catalog.rw_schemas WHERE name = 'public') AND is_index = false`
|
|
231
|
+
)
|
|
232
|
+
return new Set(result.rows.map((r: any) => r.name))
|
|
233
|
+
} catch {
|
|
234
|
+
// Fallback: if catalog query fails, return empty set
|
|
235
|
+
return new Set()
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
210
239
|
private async getExistingSubscriptions(): Promise<Set<string>> {
|
|
211
240
|
const result = await this.client!.query(
|
|
212
241
|
`SELECT name FROM rw_catalog.rw_subscriptions WHERE schema_id = (SELECT id FROM rw_catalog.rw_schemas WHERE name = 'public')`
|
|
@@ -304,4 +333,75 @@ export class DdlManager {
|
|
|
304
333
|
}
|
|
305
334
|
}
|
|
306
335
|
}
|
|
336
|
+
|
|
337
|
+
private async createCdcSource(
|
|
338
|
+
sourceName: string,
|
|
339
|
+
tableName: string,
|
|
340
|
+
source: PostgresCdcSource
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
const cdcTableName = `${sourceName}_${tableName}`
|
|
343
|
+
const slotName = source.slotName ?? `wavelet_${sourceName}`
|
|
344
|
+
const pubName = source.publicationName ?? `wavelet_${sourceName}_pub`
|
|
345
|
+
|
|
346
|
+
// RisingWave CDC source syntax:
|
|
347
|
+
// CREATE TABLE table_name ( ... ) WITH (
|
|
348
|
+
// connector = 'postgres-cdc',
|
|
349
|
+
// hostname = '...',
|
|
350
|
+
// port = '...',
|
|
351
|
+
// username = '...',
|
|
352
|
+
// password = '...',
|
|
353
|
+
// database.name = '...',
|
|
354
|
+
// table.name = '...',
|
|
355
|
+
// slot.name = '...',
|
|
356
|
+
// publication.name = '...'
|
|
357
|
+
// )
|
|
358
|
+
// We parse the connection string to extract components.
|
|
359
|
+
|
|
360
|
+
const parsed = parsePostgresUrl(source.connection)
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
await this.client!.query(`
|
|
364
|
+
CREATE TABLE IF NOT EXISTS ${cdcTableName} (*)
|
|
365
|
+
WITH (
|
|
366
|
+
connector = 'postgres-cdc',
|
|
367
|
+
hostname = '${parsed.host}',
|
|
368
|
+
port = '${parsed.port}',
|
|
369
|
+
username = '${parsed.user}',
|
|
370
|
+
password = '${parsed.password}',
|
|
371
|
+
database.name = '${parsed.database}',
|
|
372
|
+
schema.name = '${parsed.schema}',
|
|
373
|
+
table.name = '${tableName}',
|
|
374
|
+
slot.name = '${slotName}',
|
|
375
|
+
publication.name = '${pubName}'
|
|
376
|
+
)
|
|
377
|
+
`)
|
|
378
|
+
console.log(`[ddl-manager] Created CDC source: ${cdcTableName} (from ${parsed.host}/${parsed.database}.${tableName})`)
|
|
379
|
+
} catch (err: any) {
|
|
380
|
+
if (err.message?.includes('already exists')) {
|
|
381
|
+
console.log(`[ddl-manager] CDC source already exists: ${cdcTableName}`)
|
|
382
|
+
} else {
|
|
383
|
+
throw err
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function parsePostgresUrl(url: string): {
|
|
390
|
+
host: string
|
|
391
|
+
port: string
|
|
392
|
+
user: string
|
|
393
|
+
password: string
|
|
394
|
+
database: string
|
|
395
|
+
schema: string
|
|
396
|
+
} {
|
|
397
|
+
// Parse postgresql://user:password@host:port/database?schema=xxx
|
|
398
|
+
const u = new URL(url)
|
|
399
|
+
return {
|
|
400
|
+
host: u.hostname,
|
|
401
|
+
port: u.port || '5432',
|
|
402
|
+
user: decodeURIComponent(u.username),
|
|
403
|
+
password: decodeURIComponent(u.password),
|
|
404
|
+
database: u.pathname.replace(/^\//, '') || 'postgres',
|
|
405
|
+
schema: u.searchParams.get('schema') ?? 'public',
|
|
406
|
+
}
|
|
307
407
|
}
|