@eltjs/source-microsoft-sql-server 0.0.0-stage → 0.1.0
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/LICENSE +202 -0
- package/README.md +216 -2
- package/dist/change-tracking-stream.d.ts +11 -0
- package/dist/change-tracking-stream.js +149 -0
- package/dist/full-refresh-stream.d.ts +12 -0
- package/dist/full-refresh-stream.js +25 -0
- package/dist/rowversion-stream.d.ts +11 -0
- package/dist/rowversion-stream.js +70 -0
- package/dist/snapshot-stream.d.ts +12 -0
- package/dist/snapshot-stream.js +38 -0
- package/dist/sql-server-copies.d.ts +10 -0
- package/dist/sql-server-copies.js +25 -0
- package/dist/sql-server-fields.d.ts +11 -0
- package/dist/sql-server-fields.js +172 -0
- package/dist/sql-server-source.d.ts +32 -0
- package/dist/sql-server-source.js +113 -0
- package/dist/sql-server-stream.d.ts +27 -0
- package/dist/sql-server-stream.js +65 -0
- package/dist/watch-gauges.d.ts +13 -0
- package/dist/watch-gauges.js +132 -0
- package/dist/whole-table-gauges.d.ts +10 -0
- package/dist/whole-table-gauges.js +31 -0
- package/package.json +59 -4
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Collector, Latch, Period, } from '@zukhruf/async';
|
|
2
|
+
// A value of each table, by its quoted name, from one query.
|
|
3
|
+
class TableValues extends Collector {
|
|
4
|
+
#read;
|
|
5
|
+
constructor(read) {
|
|
6
|
+
super();
|
|
7
|
+
this.#read = read;
|
|
8
|
+
}
|
|
9
|
+
collect() {
|
|
10
|
+
return this.#read();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// The two versions of the database, from one query.
|
|
14
|
+
class DatabaseVersion extends Collector {
|
|
15
|
+
#session;
|
|
16
|
+
constructor(session) {
|
|
17
|
+
super();
|
|
18
|
+
this.#session = session;
|
|
19
|
+
}
|
|
20
|
+
async collect() {
|
|
21
|
+
const { changeTracking, rowversion } = await (await this.#session.open()).version();
|
|
22
|
+
return new Map([
|
|
23
|
+
['changeTracking', changeTracking],
|
|
24
|
+
['rowversion', rowversion],
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// The gauges of one watch, over one session that the first read that needs it
|
|
29
|
+
// opens, so a refused login fails the gauges of that check, not the watch. A
|
|
30
|
+
// collector reads in a check only when a watched stream names one of its
|
|
31
|
+
// gauges.
|
|
32
|
+
export class WatchGauges {
|
|
33
|
+
// The statistics of each table: SQL Server counts the rows that an insert,
|
|
34
|
+
// a delete, a TRUNCATE or an update of a column with statistics changes,
|
|
35
|
+
// and keeps the count through a restart. An update of a column with no
|
|
36
|
+
// statistics moves nothing.
|
|
37
|
+
statistics;
|
|
38
|
+
// How many rows each table holds: an insert, a delete or a TRUNCATE moves
|
|
39
|
+
// it, also in a table whose statistics SQL Server has not built yet, which
|
|
40
|
+
// count nothing, and in a tracked table, whose Change Tracking version a
|
|
41
|
+
// TRUNCATE does not move. An update moves nothing.
|
|
42
|
+
rowCounts;
|
|
43
|
+
// When SQL Server last wrote to each table: any insert, update or delete
|
|
44
|
+
// moves it, also of a column with no statistics.
|
|
45
|
+
lastWrites;
|
|
46
|
+
// The database's last committed Change Tracking version: each change to a
|
|
47
|
+
// tracked table moves it, in the transaction of the change. A TRUNCATE
|
|
48
|
+
// does not.
|
|
49
|
+
changeTracking;
|
|
50
|
+
// The database's last rowversion (@@DBTS): each insert or update of a row
|
|
51
|
+
// with a rowversion column moves it.
|
|
52
|
+
rowversion;
|
|
53
|
+
// The whole hours since the watch started: a stream that names it reloads
|
|
54
|
+
// once an hour, which finds a change that its other gauges cannot see.
|
|
55
|
+
hour = new Period(60 * 60_000);
|
|
56
|
+
#session;
|
|
57
|
+
constructor(database) {
|
|
58
|
+
const session = new WatchSession(database);
|
|
59
|
+
this.#session = session;
|
|
60
|
+
this.statistics = new TableValues(async () => (await session.open()).statistics());
|
|
61
|
+
this.rowCounts = new TableValues(async () => (await session.open()).rowCounts());
|
|
62
|
+
this.lastWrites = new TableValues(async () => (await session.open()).lastWrites());
|
|
63
|
+
const version = new DatabaseVersion(session);
|
|
64
|
+
this.changeTracking = version.gauge('changeTracking');
|
|
65
|
+
this.rowversion = version.gauge('rowversion');
|
|
66
|
+
}
|
|
67
|
+
[Symbol.asyncDispose]() {
|
|
68
|
+
return this.#session[Symbol.asyncDispose]();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// The session of one watch. An open that fails, for example because the
|
|
72
|
+
// server cannot be reached now, leaves it closed, so the next check opens it
|
|
73
|
+
// again.
|
|
74
|
+
class WatchSession {
|
|
75
|
+
#database;
|
|
76
|
+
#state;
|
|
77
|
+
constructor(database) {
|
|
78
|
+
this.#database = database;
|
|
79
|
+
this.#state = new Closed(database);
|
|
80
|
+
}
|
|
81
|
+
// The reads of one check share one open.
|
|
82
|
+
open() {
|
|
83
|
+
const state = this.#state.open(() => {
|
|
84
|
+
if (this.#state === state)
|
|
85
|
+
this.#state = new Closed(this.#database);
|
|
86
|
+
});
|
|
87
|
+
this.#state = state;
|
|
88
|
+
return state.session();
|
|
89
|
+
}
|
|
90
|
+
// Each stream's gauge of the watch closes the session when the pipeline
|
|
91
|
+
// disposes it: the first one closes it, and the others find it closed.
|
|
92
|
+
async [Symbol.asyncDispose]() {
|
|
93
|
+
const state = this.#state;
|
|
94
|
+
this.#state = new Closed(this.#database);
|
|
95
|
+
await state[Symbol.asyncDispose]();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
class Closed {
|
|
99
|
+
#database;
|
|
100
|
+
constructor(database) {
|
|
101
|
+
this.#database = database;
|
|
102
|
+
}
|
|
103
|
+
open(failed) {
|
|
104
|
+
return new Opening(this.#database, failed);
|
|
105
|
+
}
|
|
106
|
+
// Nothing is open.
|
|
107
|
+
async [Symbol.asyncDispose]() { }
|
|
108
|
+
}
|
|
109
|
+
class Opening {
|
|
110
|
+
#opened = new Latch();
|
|
111
|
+
constructor(database, failed) {
|
|
112
|
+
database.open().then((value) => this.#opened.open({ status: 'fulfilled', value }), (reason) => {
|
|
113
|
+
failed();
|
|
114
|
+
this.#opened.open({ status: 'rejected', reason });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
open() {
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
async session() {
|
|
121
|
+
const opened = await this.#opened.wait();
|
|
122
|
+
if (opened.status === 'rejected')
|
|
123
|
+
throw opened.reason;
|
|
124
|
+
return opened.value;
|
|
125
|
+
}
|
|
126
|
+
// An open that failed has nothing to close; its gauges gave its error.
|
|
127
|
+
async [Symbol.asyncDispose]() {
|
|
128
|
+
const opened = await this.#opened.wait();
|
|
129
|
+
if (opened.status === 'fulfilled')
|
|
130
|
+
await opened.value[Symbol.asyncDispose]();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Gauge } from '@zukhruf/async';
|
|
2
|
+
import { type SqlServerSession, type SqlServerTable } from '@eltjs/sdk-microsoft-sql-server';
|
|
3
|
+
import type { WatchGauges } from './watch-gauges.ts';
|
|
4
|
+
export declare class WholeTableGauges {
|
|
5
|
+
#private;
|
|
6
|
+
readonly hint: string;
|
|
7
|
+
private constructor();
|
|
8
|
+
static discover(session: SqlServerSession): Promise<WholeTableGauges>;
|
|
9
|
+
gauges(table: SqlServerTable, watch: WatchGauges): readonly Gauge[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SqlServerPermissionError, } from '@eltjs/sdk-microsoft-sql-server';
|
|
2
|
+
// The gauges of a stream that reads its whole table: its statistics, its
|
|
3
|
+
// row count, when the login may read them its last write, and the hour.
|
|
4
|
+
// Without the last write, an update of a column with no statistics waits for
|
|
5
|
+
// the hour, and hint says which grant would wake the stream at once.
|
|
6
|
+
export class WholeTableGauges {
|
|
7
|
+
hint;
|
|
8
|
+
#collectors;
|
|
9
|
+
constructor(collectors, hint) {
|
|
10
|
+
this.#collectors = collectors;
|
|
11
|
+
this.hint = hint;
|
|
12
|
+
}
|
|
13
|
+
// Tries to read the last writes once, as a watch would.
|
|
14
|
+
static async discover(session) {
|
|
15
|
+
try {
|
|
16
|
+
await session.lastWrites();
|
|
17
|
+
return new WholeTableGauges(['statistics', 'rowCounts', 'lastWrites'], '');
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (!(error instanceof SqlServerPermissionError))
|
|
21
|
+
throw error;
|
|
22
|
+
return new WholeTableGauges(['statistics', 'rowCounts'], ` Granting the login ${error.grant} would wake a watch on any write, also to a column with no statistics.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
gauges(table, watch) {
|
|
26
|
+
return [
|
|
27
|
+
...this.#collectors.map((name) => watch[name].gauge(table.quoted)),
|
|
28
|
+
watch.hour,
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eltjs/source-microsoft-sql-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An elt source for one SQL Server database: each user table that the login can read becomes a stream.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/ezzabuzaid/elt",
|
|
9
|
+
"directory": "packages/sources/microsoft/sql-server"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/ezzabuzaid/elt/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/ezzabuzaid/elt/tree/main/packages/sources/microsoft/sql-server#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"elt",
|
|
17
|
+
"sql-server",
|
|
18
|
+
"mssql",
|
|
19
|
+
"azure-sql",
|
|
20
|
+
"source",
|
|
21
|
+
"change-tracking"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
"./package.json": "./package.json",
|
|
26
|
+
"./sql-server-copies": {
|
|
27
|
+
"types": "./dist/sql-server-copies.d.ts",
|
|
28
|
+
"default": "./dist/sql-server-copies.js"
|
|
29
|
+
},
|
|
30
|
+
"./sql-server-source": {
|
|
31
|
+
"types": "./dist/sql-server-source.d.ts",
|
|
32
|
+
"default": "./dist/sql-server-source.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"!dist/spec/**",
|
|
38
|
+
"!**/*.tsbuildinfo",
|
|
39
|
+
"!**/*.test.*"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=24"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@eltjs/elt": "^0.1.0",
|
|
49
|
+
"@eltjs/sdk-microsoft-sql-server": "^0.1.0",
|
|
50
|
+
"@zukhruf/async": "^0.8.10",
|
|
51
|
+
"@zukhruf/snapshot-differential": "^0.8.10"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@sinonjs/fake-timers": "15.4.0",
|
|
55
|
+
"@types/mssql": "^12.3.0",
|
|
56
|
+
"@eltjs/elt-postgresql": "0.0.1",
|
|
57
|
+
"@zukhruf/testing": "^0.8.10",
|
|
58
|
+
"mssql": "^12.7.4",
|
|
59
|
+
"postgres": "^3.4.9"
|
|
60
|
+
}
|
|
61
|
+
}
|