js-bao 0.12.0-alpha.0 → 0.12.0-alpha.1
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/README.md +12 -0
- package/dist/cloudflare-do.cjs +213 -40
- package/dist/cloudflare-do.d.cts +78 -20
- package/dist/cloudflare-do.d.ts +78 -20
- package/dist/cloudflare-do.js +213 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1559,6 +1559,18 @@ See the `examples/` directory for complete working examples:
|
|
|
1559
1559
|
Changes to the on-disk behaviour of `js-bao/cloudflare/do` (`DurableObjectEngine`)
|
|
1560
1560
|
that a store's owner can observe. Newest first.
|
|
1561
1561
|
|
|
1562
|
+
- **`idx_records_type` is retired** (#3876). The primary key `(_type, _id)`
|
|
1563
|
+
already leads with `_type`, so a new store no longer builds the index and an
|
|
1564
|
+
existing one loses it on its next open, removed by definition under any name
|
|
1565
|
+
(an ingest-suffixed copy included); a record insert writes one row fewer.
|
|
1566
|
+
Dropping a field index or a unique constraint keeps the physical index while
|
|
1567
|
+
another registration still maps to the same definition, under the name it
|
|
1568
|
+
already has; a later index whose canonical name that kept index holds is
|
|
1569
|
+
created as `<canonical name>_2` (or the next free suffix). Duplicate copies of a
|
|
1570
|
+
definition the engine owns (base, retired or registered) collapse to one on
|
|
1571
|
+
open; an index created outside the engine is never dropped. Index definitions
|
|
1572
|
+
now compare with quoted literals verbatim, so `WHERE _type = 'a b'` and
|
|
1573
|
+
`WHERE _type = 'ab'` are different indexes. Client stores are unchanged.
|
|
1562
1574
|
- **`_indexes.created_at` reports the first registration** (#3875). Re-registering
|
|
1563
1575
|
a field index whose `field_type` and `is_unique` are unchanged no longer
|
|
1564
1576
|
rewrites its `_indexes` row: it writes no row, and `created_at` keeps the time
|
package/dist/cloudflare-do.cjs
CHANGED
|
@@ -2658,6 +2658,7 @@ __export(cloudflare_do_exports, {
|
|
|
2658
2658
|
checkAutoStampsReservedFields: () => checkAutoStampsReservedFields,
|
|
2659
2659
|
checkReservedFieldName: () => checkReservedFieldName,
|
|
2660
2660
|
clearMetaSyncCache: () => clearMetaSyncCache,
|
|
2661
|
+
collapseDuplicateIndexes: () => collapseDuplicateIndexes,
|
|
2661
2662
|
compareRecordIds: () => compareRecordIds,
|
|
2662
2663
|
completeOverlayEntries: () => completeOverlayEntries,
|
|
2663
2664
|
createDatabaseDO: () => createDatabaseDO,
|
|
@@ -2677,6 +2678,7 @@ __export(cloudflare_do_exports, {
|
|
|
2677
2678
|
format2StringSetMembers: () => format2StringSetMembers,
|
|
2678
2679
|
groupOverlayEntries: () => groupOverlayEntries,
|
|
2679
2680
|
handleRequest: () => handleRequest,
|
|
2681
|
+
indexDefinitionKey: () => indexDefinitionKey,
|
|
2680
2682
|
inferFieldType: () => inferFieldType,
|
|
2681
2683
|
isAnchorId: () => isAnchorId,
|
|
2682
2684
|
isFormat2StringSetYMap: () => isFormat2StringSetYMap,
|
|
@@ -2841,6 +2843,11 @@ var DatabaseEngine = class {
|
|
|
2841
2843
|
// src/schema/JsonSchemaDDL.ts
|
|
2842
2844
|
init_sql();
|
|
2843
2845
|
init_sql();
|
|
2846
|
+
var RECORDS_TYPE_INDEX_SPEC = {
|
|
2847
|
+
table: "records",
|
|
2848
|
+
expressions: "_type",
|
|
2849
|
+
canonicalName: "idx_records_type"
|
|
2850
|
+
};
|
|
2844
2851
|
var JsonSchemaDDL = class {
|
|
2845
2852
|
/**
|
|
2846
2853
|
* Generate CREATE TABLE statement for the records table
|
|
@@ -2924,14 +2931,16 @@ CREATE TABLE IF NOT EXISTS stringset_index (
|
|
|
2924
2931
|
* A document whose tables were swapped by a bulk-load ingest carries these
|
|
2925
2932
|
* under session-suffixed names; creating them by name on the next cold open
|
|
2926
2933
|
* would build a second copy of each over the whole table.
|
|
2934
|
+
*
|
|
2935
|
+
* `records(_type)` is a base index only in yjs mode (#3876). The server's
|
|
2936
|
+
* primary key `(_type, _id)` already leads with `_type`, so on a
|
|
2937
|
+
* `DurableObjectEngine` the index served no plan the primary key's autoindex
|
|
2938
|
+
* could not and cost a row on every insert; the yjs-mode list stays in step
|
|
2939
|
+
* with the yjs-mode DDL, which clients keep.
|
|
2927
2940
|
*/
|
|
2928
2941
|
static baseIndexSpecs(options) {
|
|
2929
2942
|
const specs = [
|
|
2930
|
-
{
|
|
2931
|
-
table: "records",
|
|
2932
|
-
expressions: "_type",
|
|
2933
|
-
canonicalName: "idx_records_type"
|
|
2934
|
-
},
|
|
2943
|
+
...options.includeDocIdColumn ? [{ ...RECORDS_TYPE_INDEX_SPEC }] : [],
|
|
2935
2944
|
{
|
|
2936
2945
|
table: "stringset_index",
|
|
2937
2946
|
expressions: "_type, field, value",
|
|
@@ -2960,22 +2969,28 @@ CREATE TABLE IF NOT EXISTS stringset_index (
|
|
|
2960
2969
|
return specs;
|
|
2961
2970
|
}
|
|
2962
2971
|
/**
|
|
2963
|
-
* The base indexes
|
|
2972
|
+
* The base indexes RETIRED, as specs, each removed BY DEFINITION for the
|
|
2973
|
+
* same reason the others are created that way: a document a bulk-load
|
|
2974
|
+
* ingest swapped carries it under a session-suffixed name, which a
|
|
2975
|
+
* `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
|
|
2964
2976
|
*
|
|
2965
|
-
* `(_type, field, _record_id)` is a
|
|
2966
|
-
* autoindex, so it never served a
|
|
2967
|
-
*
|
|
2968
|
-
*
|
|
2969
|
-
*
|
|
2970
|
-
*
|
|
2977
|
+
* - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
|
|
2978
|
+
* left prefix of the member table's UNIQUE autoindex, so it never served a
|
|
2979
|
+
* plan the autoindex could not; what it did do was cost a second index
|
|
2980
|
+
* write per member insert.
|
|
2981
|
+
* - `idx_records_type` (#3876), on the server only: the primary key
|
|
2982
|
+
* `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
|
|
2983
|
+
* its autoindex (covering, for a count), and the index cost a row on every
|
|
2984
|
+
* insert. Clients' yjs-mode DDL keeps it.
|
|
2971
2985
|
*/
|
|
2972
|
-
static retiredBaseIndexSpecs(
|
|
2986
|
+
static retiredBaseIndexSpecs(options) {
|
|
2973
2987
|
return [
|
|
2974
2988
|
{
|
|
2975
2989
|
table: "stringset_index",
|
|
2976
2990
|
expressions: "_type, field, _record_id",
|
|
2977
2991
|
canonicalName: "idx_ss_type_field_record"
|
|
2978
|
-
}
|
|
2992
|
+
},
|
|
2993
|
+
...options.includeDocIdColumn ? [] : [{ ...RECORDS_TYPE_INDEX_SPEC }]
|
|
2979
2994
|
];
|
|
2980
2995
|
}
|
|
2981
2996
|
/**
|
|
@@ -3421,55 +3436,134 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
3421
3436
|
init_sql();
|
|
3422
3437
|
|
|
3423
3438
|
// src/utils/physicalIndex.ts
|
|
3439
|
+
init_sql();
|
|
3424
3440
|
var INDEX_SQL = /^\s*CREATE\s+(UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?"?([A-Za-z0-9_$]+)"?\s+ON\s+"?([A-Za-z0-9_$]+)"?\s*\(([\s\S]*)\)\s*(?:WHERE\s+([\s\S]+))?$/i;
|
|
3441
|
+
var TIGHT = /* @__PURE__ */ new Set(["(", ")", ",", "=", "|"]);
|
|
3442
|
+
function normalizeSqlText(text) {
|
|
3443
|
+
let out = "";
|
|
3444
|
+
let pendingSpace = false;
|
|
3445
|
+
let i = 0;
|
|
3446
|
+
const emit = (chunk) => {
|
|
3447
|
+
const spaced = pendingSpace && out.length > 0 && !TIGHT.has(out[out.length - 1]) && !TIGHT.has(chunk[0]);
|
|
3448
|
+
if (spaced) out += " ";
|
|
3449
|
+
pendingSpace = false;
|
|
3450
|
+
out += chunk;
|
|
3451
|
+
};
|
|
3452
|
+
while (i < text.length) {
|
|
3453
|
+
const ch = text[i];
|
|
3454
|
+
if (ch === "'" || ch === '"') {
|
|
3455
|
+
let j = i + 1;
|
|
3456
|
+
for (; ; ) {
|
|
3457
|
+
if (j >= text.length) break;
|
|
3458
|
+
if (text[j] === ch) {
|
|
3459
|
+
if (text[j + 1] === ch) {
|
|
3460
|
+
j += 2;
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
break;
|
|
3464
|
+
}
|
|
3465
|
+
j++;
|
|
3466
|
+
}
|
|
3467
|
+
emit(text.slice(i, j + 1));
|
|
3468
|
+
i = j + 1;
|
|
3469
|
+
continue;
|
|
3470
|
+
}
|
|
3471
|
+
if (/\s/.test(ch)) {
|
|
3472
|
+
pendingSpace = true;
|
|
3473
|
+
i++;
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
emit(ch);
|
|
3477
|
+
i++;
|
|
3478
|
+
}
|
|
3479
|
+
return out;
|
|
3480
|
+
}
|
|
3425
3481
|
function shapeOf(options) {
|
|
3426
|
-
const strip = (text) => text.replace(/\s+/g, "");
|
|
3427
3482
|
return [
|
|
3428
3483
|
options.unique ? "UNIQUE" : "INDEX",
|
|
3429
|
-
|
|
3430
|
-
options.where ?
|
|
3431
|
-
].join("
|
|
3484
|
+
normalizeSqlText(options.expressions),
|
|
3485
|
+
options.where ? normalizeSqlText(options.where) : ""
|
|
3486
|
+
].join(" ");
|
|
3432
3487
|
}
|
|
3433
|
-
function
|
|
3488
|
+
function indexDefinitionKey(spec) {
|
|
3434
3489
|
return shapeOf({
|
|
3435
3490
|
unique: Boolean(spec.unique),
|
|
3436
3491
|
expressions: spec.expressions,
|
|
3437
3492
|
where: spec.where ?? null
|
|
3438
3493
|
});
|
|
3439
3494
|
}
|
|
3440
|
-
function
|
|
3441
|
-
const wanted = shapeOfSpec(spec);
|
|
3495
|
+
function namedIndexesOf(exec, table) {
|
|
3442
3496
|
const rows = exec(
|
|
3443
3497
|
`SELECT name, sql FROM sqlite_master
|
|
3444
|
-
WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
|
|
3445
|
-
|
|
3498
|
+
WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
|
|
3499
|
+
ORDER BY name`,
|
|
3500
|
+
[table]
|
|
3446
3501
|
);
|
|
3502
|
+
const named = [];
|
|
3447
3503
|
for (const row of rows) {
|
|
3448
3504
|
const match = INDEX_SQL.exec(String(row.sql));
|
|
3449
3505
|
if (!match) continue;
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3506
|
+
named.push({
|
|
3507
|
+
name: String(row.name),
|
|
3508
|
+
key: shapeOf({
|
|
3509
|
+
unique: Boolean(match[1]),
|
|
3510
|
+
expressions: match[4] ?? "",
|
|
3511
|
+
where: match[5] ?? null
|
|
3512
|
+
})
|
|
3454
3513
|
});
|
|
3455
|
-
if (shape === wanted) return String(row.name);
|
|
3456
3514
|
}
|
|
3457
|
-
return
|
|
3515
|
+
return named;
|
|
3516
|
+
}
|
|
3517
|
+
function resolvePhysicalIndex(exec, spec) {
|
|
3518
|
+
const wanted = indexDefinitionKey(spec);
|
|
3519
|
+
const found = namedIndexesOf(exec, spec.table).find(
|
|
3520
|
+
(index) => index.key === wanted
|
|
3521
|
+
);
|
|
3522
|
+
return found ? found.name : null;
|
|
3523
|
+
}
|
|
3524
|
+
function unusedIndexName(exec, canonicalName) {
|
|
3525
|
+
const taken = (name) => exec("SELECT 1 FROM sqlite_master WHERE name = ? COLLATE NOCASE", [name]).length > 0;
|
|
3526
|
+
if (!taken(canonicalName)) return canonicalName;
|
|
3527
|
+
for (let n = 2; ; n++) {
|
|
3528
|
+
const candidate = `${canonicalName}_${n}`;
|
|
3529
|
+
if (!taken(candidate)) return candidate;
|
|
3530
|
+
}
|
|
3458
3531
|
}
|
|
3459
3532
|
function ensurePhysicalIndex(exec, spec) {
|
|
3460
3533
|
if (resolvePhysicalIndex(exec, spec) !== null) return "exists";
|
|
3461
3534
|
exec(
|
|
3462
|
-
`CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${spec.canonicalName} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
|
|
3535
|
+
`CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${unusedIndexName(exec, spec.canonicalName)} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
|
|
3463
3536
|
);
|
|
3464
3537
|
return "created";
|
|
3465
3538
|
}
|
|
3466
3539
|
function removePhysicalIndex(exec, spec) {
|
|
3467
|
-
const
|
|
3468
|
-
|
|
3469
|
-
|
|
3540
|
+
const wanted = indexDefinitionKey(spec);
|
|
3541
|
+
for (const index of namedIndexesOf(exec, spec.table)) {
|
|
3542
|
+
if (index.key !== wanted) continue;
|
|
3543
|
+
exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
function collapseDuplicateIndexes(exec, table, ownedKeys) {
|
|
3547
|
+
const kept = /* @__PURE__ */ new Set();
|
|
3548
|
+
const dropped = [];
|
|
3549
|
+
for (const index of namedIndexesOf(exec, table)) {
|
|
3550
|
+
if (!ownedKeys.has(index.key)) continue;
|
|
3551
|
+
if (!kept.has(index.key)) {
|
|
3552
|
+
kept.add(index.key);
|
|
3553
|
+
continue;
|
|
3554
|
+
}
|
|
3555
|
+
exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
|
|
3556
|
+
dropped.push(index.name);
|
|
3557
|
+
}
|
|
3558
|
+
return dropped;
|
|
3470
3559
|
}
|
|
3471
3560
|
|
|
3472
3561
|
// src/engines/cloudflare/DurableObjectEngine.ts
|
|
3562
|
+
function ownedDefinitionKeys(specs, table) {
|
|
3563
|
+
return new Set(
|
|
3564
|
+
specs.filter((spec) => spec.table === table).map(indexDefinitionKey)
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3473
3567
|
var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
3474
3568
|
sql;
|
|
3475
3569
|
storage;
|
|
@@ -3522,8 +3616,85 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
3522
3616
|
for (const spec of JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions)) {
|
|
3523
3617
|
removePhysicalIndex(this.indexExec, spec);
|
|
3524
3618
|
}
|
|
3619
|
+
const owned = [
|
|
3620
|
+
...JsonSchemaDDL.baseIndexSpecs(this.schemaOptions),
|
|
3621
|
+
...JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions),
|
|
3622
|
+
...this.registeredIndexSpecs()
|
|
3623
|
+
];
|
|
3624
|
+
for (const table of ["records", "stringset_index"]) {
|
|
3625
|
+
const dropped = collapseDuplicateIndexes(
|
|
3626
|
+
this.indexExec,
|
|
3627
|
+
table,
|
|
3628
|
+
ownedDefinitionKeys(owned, table)
|
|
3629
|
+
);
|
|
3630
|
+
if (dropped.length > 0) {
|
|
3631
|
+
console.info("Durable Object engine collapsed duplicate indexes", {
|
|
3632
|
+
table,
|
|
3633
|
+
dropped
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3525
3637
|
this.initialized = true;
|
|
3526
3638
|
}
|
|
3639
|
+
/**
|
|
3640
|
+
* The index each registration row maps to (#3876): `fieldIndexSpec` per
|
|
3641
|
+
* `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
|
|
3642
|
+
* A row whose `fields_json` does not parse, or whose names are not valid
|
|
3643
|
+
* identifiers, maps to nothing.
|
|
3644
|
+
*/
|
|
3645
|
+
registeredIndexSpecs() {
|
|
3646
|
+
const specs = [];
|
|
3647
|
+
try {
|
|
3648
|
+
for (const row of this.execSqlSync(
|
|
3649
|
+
"SELECT model_name, field_name FROM _indexes"
|
|
3650
|
+
)) {
|
|
3651
|
+
try {
|
|
3652
|
+
specs.push(
|
|
3653
|
+
JsonSchemaDDL.fieldIndexSpec(
|
|
3654
|
+
String(row.model_name),
|
|
3655
|
+
String(row.field_name)
|
|
3656
|
+
)
|
|
3657
|
+
);
|
|
3658
|
+
} catch (e) {
|
|
3659
|
+
console.debug("Skipping an unusable index registration:", e);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
} catch (e) {
|
|
3663
|
+
console.debug("Could not read index registrations:", e);
|
|
3664
|
+
}
|
|
3665
|
+
try {
|
|
3666
|
+
for (const row of this.execSqlSync(
|
|
3667
|
+
"SELECT model_name, constraint_name, fields_json FROM _unique_constraints"
|
|
3668
|
+
)) {
|
|
3669
|
+
try {
|
|
3670
|
+
specs.push(
|
|
3671
|
+
JsonSchemaDDL.uniqueConstraintIndexSpec(
|
|
3672
|
+
String(row.model_name),
|
|
3673
|
+
String(row.constraint_name),
|
|
3674
|
+
JSON.parse(String(row.fields_json))
|
|
3675
|
+
)
|
|
3676
|
+
);
|
|
3677
|
+
} catch (e) {
|
|
3678
|
+
console.debug("Skipping an unusable unique-constraint registration:", e);
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
} catch (e) {
|
|
3682
|
+
console.debug("Could not read unique-constraint registrations:", e);
|
|
3683
|
+
}
|
|
3684
|
+
return specs;
|
|
3685
|
+
}
|
|
3686
|
+
/**
|
|
3687
|
+
* Whether a remaining registration still maps to `spec`'s definition
|
|
3688
|
+
* (#3876). A field index and a unique constraint over the same field — or
|
|
3689
|
+
* two constraints over the same fields — share one physical index since
|
|
3690
|
+
* #3434, so a drop removes it only when no other declaration needs it. The
|
|
3691
|
+
* count is derived from the registration tables at drop time, not stored.
|
|
3692
|
+
*/
|
|
3693
|
+
definitionStillDeclared(spec) {
|
|
3694
|
+
return ownedDefinitionKeys(this.registeredIndexSpecs(), spec.table).has(
|
|
3695
|
+
indexDefinitionKey(spec)
|
|
3696
|
+
);
|
|
3697
|
+
}
|
|
3527
3698
|
/**
|
|
3528
3699
|
* Whether this open renamed the pre-underscore column names.
|
|
3529
3700
|
*
|
|
@@ -3679,10 +3850,9 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
3679
3850
|
"DELETE FROM _indexes WHERE model_name = ? AND field_name = ?",
|
|
3680
3851
|
[modelName, fieldName]
|
|
3681
3852
|
);
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
);
|
|
3853
|
+
const spec = JsonSchemaDDL.fieldIndexSpec(modelName, fieldName);
|
|
3854
|
+
if (this.definitionStillDeclared(spec)) return;
|
|
3855
|
+
removePhysicalIndex(this.indexExec, spec);
|
|
3686
3856
|
}
|
|
3687
3857
|
/**
|
|
3688
3858
|
* List all registered indexes, optionally filtered by model.
|
|
@@ -3747,10 +3917,13 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
3747
3917
|
} catch {
|
|
3748
3918
|
return;
|
|
3749
3919
|
}
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3920
|
+
const spec = JsonSchemaDDL.uniqueConstraintIndexSpec(
|
|
3921
|
+
modelName,
|
|
3922
|
+
constraintName,
|
|
3923
|
+
fields
|
|
3753
3924
|
);
|
|
3925
|
+
if (this.definitionStillDeclared(spec)) return;
|
|
3926
|
+
removePhysicalIndex(this.indexExec, spec);
|
|
3754
3927
|
}
|
|
3755
3928
|
/**
|
|
3756
3929
|
* List composite unique constraints, optionally filtered by model.
|
package/dist/cloudflare-do.d.cts
CHANGED
|
@@ -2096,12 +2096,16 @@ declare abstract class DatabaseEngine {
|
|
|
2096
2096
|
*
|
|
2097
2097
|
* ## What counts as the same definition
|
|
2098
2098
|
*
|
|
2099
|
-
* The expression list and the `WHERE` clause, with whitespace
|
|
2100
|
-
*
|
|
2099
|
+
* The expression list and the `WHERE` clause, with whitespace normalised
|
|
2100
|
+
* OUTSIDE quoted literals and every literal compared verbatim, CASE PRESERVED
|
|
2101
|
+
* throughout, plus whether the index is UNIQUE. Case matters: a partial
|
|
2101
2102
|
* index's predicate carries a model name (`WHERE _type = 'Note'`), and `Note`
|
|
2102
|
-
* and `note` are different models.
|
|
2103
|
-
*
|
|
2104
|
-
*
|
|
2103
|
+
* and `note` are different models. Literals matter for the same reason, and
|
|
2104
|
+
* more since #3876 made the comparison drive DROPS: `WHERE _type = 'a b'` and
|
|
2105
|
+
* `WHERE _type = 'ab'` are two different partial indexes, and a comparison
|
|
2106
|
+
* that stripped the space inside the quotes would drop one for the other. The
|
|
2107
|
+
* table name is not part of the comparison because the lookup is already
|
|
2108
|
+
* scoped to one table — and because SQLite requotes it on a rename.
|
|
2105
2109
|
*/
|
|
2106
2110
|
/** The SQL executor this module needs: a query and its bindings, rows back. */
|
|
2107
2111
|
type PhysicalIndexExec = (query: string, params?: unknown[]) => Array<Record<string, any>>;
|
|
@@ -2116,27 +2120,55 @@ interface PhysicalIndexSpec {
|
|
|
2116
2120
|
/** The name to create it under when nothing equivalent exists. */
|
|
2117
2121
|
canonicalName: string;
|
|
2118
2122
|
}
|
|
2123
|
+
/**
|
|
2124
|
+
* The key two index definitions share exactly when they are the same index
|
|
2125
|
+
* (#3876): UNIQUE-ness, expression list and predicate, whitespace normalised
|
|
2126
|
+
* outside literals, literals and case preserved. What `resolvePhysicalIndex`,
|
|
2127
|
+
* `ensurePhysicalIndex`, `removePhysicalIndex`, the engine's reference-counted
|
|
2128
|
+
* drops and `collapseDuplicateIndexes` all compare.
|
|
2129
|
+
*/
|
|
2130
|
+
declare function indexDefinitionKey(spec: PhysicalIndexSpec): string;
|
|
2119
2131
|
/**
|
|
2120
2132
|
* The NAME of an index on `spec.table` with `spec`'s definition, or null.
|
|
2121
|
-
*
|
|
2122
|
-
* Autoindexes (those `sqlite_master` records with a null `sql`) are skipped:
|
|
2123
|
-
* they belong to a constraint, not to a caller, and cannot be created or
|
|
2124
|
-
* dropped by name anyway.
|
|
2133
|
+
* The first by name when there are several.
|
|
2125
2134
|
*/
|
|
2126
2135
|
declare function resolvePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): string | null;
|
|
2127
2136
|
/**
|
|
2128
2137
|
* Create `spec` under its canonical name unless an equivalent index already
|
|
2129
2138
|
* exists under some name. Answers which of the two happened.
|
|
2139
|
+
*
|
|
2140
|
+
* When the canonical name is held by something else, the index is created
|
|
2141
|
+
* under the first free `<canonicalName>_<n>`: the holder is a different
|
|
2142
|
+
* definition (an equal one would have resolved above), and every reader
|
|
2143
|
+
* resolves indexes by definition, never by name.
|
|
2130
2144
|
*/
|
|
2131
2145
|
declare function ensurePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): "created" | "exists";
|
|
2132
2146
|
/**
|
|
2133
|
-
* Drop
|
|
2147
|
+
* Drop EVERY index matching `spec`, by the names they actually have.
|
|
2134
2148
|
*
|
|
2135
2149
|
* A no-op when there is none — the callers are all idempotent "make sure this
|
|
2136
2150
|
* is gone" paths, and a swapped table's index under a foreign name is exactly
|
|
2137
|
-
* the one a `DROP INDEX IF EXISTS <canonicalName>` would miss.
|
|
2151
|
+
* the one a `DROP INDEX IF EXISTS <canonicalName>` would miss. All of them,
|
|
2152
|
+
* not the first (#3876): a store carrying the canonical copy and an
|
|
2153
|
+
* ingest-suffixed one converges in one pass. The names come from
|
|
2154
|
+
* `sqlite_master`, where a quoted keyword such as `"select"` is stored bare,
|
|
2155
|
+
* so they are quoted back.
|
|
2138
2156
|
*/
|
|
2139
2157
|
declare function removePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): void;
|
|
2158
|
+
/**
|
|
2159
|
+
* Drop the extra copies of every definition in `ownedKeys` on `table`
|
|
2160
|
+
* (#3876), keeping the first of each by name. Answers the names it dropped.
|
|
2161
|
+
*
|
|
2162
|
+
* Databases built before #3434 could carry a field index and a unique
|
|
2163
|
+
* constraint's index over one definition, and every insert and `_data` update
|
|
2164
|
+
* paid for both. Only OWNED definitions are touched — ones the caller would
|
|
2165
|
+
* itself create — so an index a consumer made by hand is never dropped, even
|
|
2166
|
+
* when it duplicates another. `ownedKeys` are the keys of definitions on
|
|
2167
|
+
* `table` itself: the key leaves the table out, so a set built across tables
|
|
2168
|
+
* would claim `stringset_index(_type)` for a spec over `records(_type)`.
|
|
2169
|
+
* `DROP INDEX` writes no row.
|
|
2170
|
+
*/
|
|
2171
|
+
declare function collapseDuplicateIndexes(exec: PhysicalIndexExec, table: string, ownedKeys: ReadonlySet<string>): string[];
|
|
2140
2172
|
|
|
2141
2173
|
/**
|
|
2142
2174
|
* JSON Schema DDL Generator
|
|
@@ -2181,19 +2213,30 @@ declare class JsonSchemaDDL {
|
|
|
2181
2213
|
* A document whose tables were swapped by a bulk-load ingest carries these
|
|
2182
2214
|
* under session-suffixed names; creating them by name on the next cold open
|
|
2183
2215
|
* would build a second copy of each over the whole table.
|
|
2216
|
+
*
|
|
2217
|
+
* `records(_type)` is a base index only in yjs mode (#3876). The server's
|
|
2218
|
+
* primary key `(_type, _id)` already leads with `_type`, so on a
|
|
2219
|
+
* `DurableObjectEngine` the index served no plan the primary key's autoindex
|
|
2220
|
+
* could not and cost a row on every insert; the yjs-mode list stays in step
|
|
2221
|
+
* with the yjs-mode DDL, which clients keep.
|
|
2184
2222
|
*/
|
|
2185
2223
|
static baseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
|
|
2186
2224
|
/**
|
|
2187
|
-
* The base indexes
|
|
2225
|
+
* The base indexes RETIRED, as specs, each removed BY DEFINITION for the
|
|
2226
|
+
* same reason the others are created that way: a document a bulk-load
|
|
2227
|
+
* ingest swapped carries it under a session-suffixed name, which a
|
|
2228
|
+
* `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
|
|
2188
2229
|
*
|
|
2189
|
-
* `(_type, field, _record_id)` is a
|
|
2190
|
-
* autoindex, so it never served a
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2193
|
-
*
|
|
2194
|
-
*
|
|
2230
|
+
* - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
|
|
2231
|
+
* left prefix of the member table's UNIQUE autoindex, so it never served a
|
|
2232
|
+
* plan the autoindex could not; what it did do was cost a second index
|
|
2233
|
+
* write per member insert.
|
|
2234
|
+
* - `idx_records_type` (#3876), on the server only: the primary key
|
|
2235
|
+
* `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
|
|
2236
|
+
* its autoindex (covering, for a count), and the index cost a row on every
|
|
2237
|
+
* insert. Clients' yjs-mode DDL keeps it.
|
|
2195
2238
|
*/
|
|
2196
|
-
static retiredBaseIndexSpecs(
|
|
2239
|
+
static retiredBaseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
|
|
2197
2240
|
/**
|
|
2198
2241
|
* Drop the retired base indexes BY NAME, for the name-based engines (#3688).
|
|
2199
2242
|
*
|
|
@@ -2644,6 +2687,21 @@ declare class DurableObjectEngine extends JsonSchemaEngine {
|
|
|
2644
2687
|
* really is identified by its name.
|
|
2645
2688
|
*/
|
|
2646
2689
|
initializeSchema(): Promise<void>;
|
|
2690
|
+
/**
|
|
2691
|
+
* The index each registration row maps to (#3876): `fieldIndexSpec` per
|
|
2692
|
+
* `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
|
|
2693
|
+
* A row whose `fields_json` does not parse, or whose names are not valid
|
|
2694
|
+
* identifiers, maps to nothing.
|
|
2695
|
+
*/
|
|
2696
|
+
private registeredIndexSpecs;
|
|
2697
|
+
/**
|
|
2698
|
+
* Whether a remaining registration still maps to `spec`'s definition
|
|
2699
|
+
* (#3876). A field index and a unique constraint over the same field — or
|
|
2700
|
+
* two constraints over the same fields — share one physical index since
|
|
2701
|
+
* #3434, so a drop removes it only when no other declaration needs it. The
|
|
2702
|
+
* count is derived from the registration tables at drop time, not stored.
|
|
2703
|
+
*/
|
|
2704
|
+
private definitionStillDeclared;
|
|
2647
2705
|
/**
|
|
2648
2706
|
* Whether this open renamed the pre-underscore column names.
|
|
2649
2707
|
*
|
|
@@ -7059,4 +7117,4 @@ declare class SnapshotPartitioner {
|
|
|
7059
7117
|
private take;
|
|
7060
7118
|
}
|
|
7061
7119
|
|
|
7062
|
-
export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
|
|
7120
|
+
export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, collapseDuplicateIndexes, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, indexDefinitionKey, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
|
package/dist/cloudflare-do.d.ts
CHANGED
|
@@ -2096,12 +2096,16 @@ declare abstract class DatabaseEngine {
|
|
|
2096
2096
|
*
|
|
2097
2097
|
* ## What counts as the same definition
|
|
2098
2098
|
*
|
|
2099
|
-
* The expression list and the `WHERE` clause, with whitespace
|
|
2100
|
-
*
|
|
2099
|
+
* The expression list and the `WHERE` clause, with whitespace normalised
|
|
2100
|
+
* OUTSIDE quoted literals and every literal compared verbatim, CASE PRESERVED
|
|
2101
|
+
* throughout, plus whether the index is UNIQUE. Case matters: a partial
|
|
2101
2102
|
* index's predicate carries a model name (`WHERE _type = 'Note'`), and `Note`
|
|
2102
|
-
* and `note` are different models.
|
|
2103
|
-
*
|
|
2104
|
-
*
|
|
2103
|
+
* and `note` are different models. Literals matter for the same reason, and
|
|
2104
|
+
* more since #3876 made the comparison drive DROPS: `WHERE _type = 'a b'` and
|
|
2105
|
+
* `WHERE _type = 'ab'` are two different partial indexes, and a comparison
|
|
2106
|
+
* that stripped the space inside the quotes would drop one for the other. The
|
|
2107
|
+
* table name is not part of the comparison because the lookup is already
|
|
2108
|
+
* scoped to one table — and because SQLite requotes it on a rename.
|
|
2105
2109
|
*/
|
|
2106
2110
|
/** The SQL executor this module needs: a query and its bindings, rows back. */
|
|
2107
2111
|
type PhysicalIndexExec = (query: string, params?: unknown[]) => Array<Record<string, any>>;
|
|
@@ -2116,27 +2120,55 @@ interface PhysicalIndexSpec {
|
|
|
2116
2120
|
/** The name to create it under when nothing equivalent exists. */
|
|
2117
2121
|
canonicalName: string;
|
|
2118
2122
|
}
|
|
2123
|
+
/**
|
|
2124
|
+
* The key two index definitions share exactly when they are the same index
|
|
2125
|
+
* (#3876): UNIQUE-ness, expression list and predicate, whitespace normalised
|
|
2126
|
+
* outside literals, literals and case preserved. What `resolvePhysicalIndex`,
|
|
2127
|
+
* `ensurePhysicalIndex`, `removePhysicalIndex`, the engine's reference-counted
|
|
2128
|
+
* drops and `collapseDuplicateIndexes` all compare.
|
|
2129
|
+
*/
|
|
2130
|
+
declare function indexDefinitionKey(spec: PhysicalIndexSpec): string;
|
|
2119
2131
|
/**
|
|
2120
2132
|
* The NAME of an index on `spec.table` with `spec`'s definition, or null.
|
|
2121
|
-
*
|
|
2122
|
-
* Autoindexes (those `sqlite_master` records with a null `sql`) are skipped:
|
|
2123
|
-
* they belong to a constraint, not to a caller, and cannot be created or
|
|
2124
|
-
* dropped by name anyway.
|
|
2133
|
+
* The first by name when there are several.
|
|
2125
2134
|
*/
|
|
2126
2135
|
declare function resolvePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): string | null;
|
|
2127
2136
|
/**
|
|
2128
2137
|
* Create `spec` under its canonical name unless an equivalent index already
|
|
2129
2138
|
* exists under some name. Answers which of the two happened.
|
|
2139
|
+
*
|
|
2140
|
+
* When the canonical name is held by something else, the index is created
|
|
2141
|
+
* under the first free `<canonicalName>_<n>`: the holder is a different
|
|
2142
|
+
* definition (an equal one would have resolved above), and every reader
|
|
2143
|
+
* resolves indexes by definition, never by name.
|
|
2130
2144
|
*/
|
|
2131
2145
|
declare function ensurePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): "created" | "exists";
|
|
2132
2146
|
/**
|
|
2133
|
-
* Drop
|
|
2147
|
+
* Drop EVERY index matching `spec`, by the names they actually have.
|
|
2134
2148
|
*
|
|
2135
2149
|
* A no-op when there is none — the callers are all idempotent "make sure this
|
|
2136
2150
|
* is gone" paths, and a swapped table's index under a foreign name is exactly
|
|
2137
|
-
* the one a `DROP INDEX IF EXISTS <canonicalName>` would miss.
|
|
2151
|
+
* the one a `DROP INDEX IF EXISTS <canonicalName>` would miss. All of them,
|
|
2152
|
+
* not the first (#3876): a store carrying the canonical copy and an
|
|
2153
|
+
* ingest-suffixed one converges in one pass. The names come from
|
|
2154
|
+
* `sqlite_master`, where a quoted keyword such as `"select"` is stored bare,
|
|
2155
|
+
* so they are quoted back.
|
|
2138
2156
|
*/
|
|
2139
2157
|
declare function removePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): void;
|
|
2158
|
+
/**
|
|
2159
|
+
* Drop the extra copies of every definition in `ownedKeys` on `table`
|
|
2160
|
+
* (#3876), keeping the first of each by name. Answers the names it dropped.
|
|
2161
|
+
*
|
|
2162
|
+
* Databases built before #3434 could carry a field index and a unique
|
|
2163
|
+
* constraint's index over one definition, and every insert and `_data` update
|
|
2164
|
+
* paid for both. Only OWNED definitions are touched — ones the caller would
|
|
2165
|
+
* itself create — so an index a consumer made by hand is never dropped, even
|
|
2166
|
+
* when it duplicates another. `ownedKeys` are the keys of definitions on
|
|
2167
|
+
* `table` itself: the key leaves the table out, so a set built across tables
|
|
2168
|
+
* would claim `stringset_index(_type)` for a spec over `records(_type)`.
|
|
2169
|
+
* `DROP INDEX` writes no row.
|
|
2170
|
+
*/
|
|
2171
|
+
declare function collapseDuplicateIndexes(exec: PhysicalIndexExec, table: string, ownedKeys: ReadonlySet<string>): string[];
|
|
2140
2172
|
|
|
2141
2173
|
/**
|
|
2142
2174
|
* JSON Schema DDL Generator
|
|
@@ -2181,19 +2213,30 @@ declare class JsonSchemaDDL {
|
|
|
2181
2213
|
* A document whose tables were swapped by a bulk-load ingest carries these
|
|
2182
2214
|
* under session-suffixed names; creating them by name on the next cold open
|
|
2183
2215
|
* would build a second copy of each over the whole table.
|
|
2216
|
+
*
|
|
2217
|
+
* `records(_type)` is a base index only in yjs mode (#3876). The server's
|
|
2218
|
+
* primary key `(_type, _id)` already leads with `_type`, so on a
|
|
2219
|
+
* `DurableObjectEngine` the index served no plan the primary key's autoindex
|
|
2220
|
+
* could not and cost a row on every insert; the yjs-mode list stays in step
|
|
2221
|
+
* with the yjs-mode DDL, which clients keep.
|
|
2184
2222
|
*/
|
|
2185
2223
|
static baseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
|
|
2186
2224
|
/**
|
|
2187
|
-
* The base indexes
|
|
2225
|
+
* The base indexes RETIRED, as specs, each removed BY DEFINITION for the
|
|
2226
|
+
* same reason the others are created that way: a document a bulk-load
|
|
2227
|
+
* ingest swapped carries it under a session-suffixed name, which a
|
|
2228
|
+
* `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
|
|
2188
2229
|
*
|
|
2189
|
-
* `(_type, field, _record_id)` is a
|
|
2190
|
-
* autoindex, so it never served a
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2193
|
-
*
|
|
2194
|
-
*
|
|
2230
|
+
* - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
|
|
2231
|
+
* left prefix of the member table's UNIQUE autoindex, so it never served a
|
|
2232
|
+
* plan the autoindex could not; what it did do was cost a second index
|
|
2233
|
+
* write per member insert.
|
|
2234
|
+
* - `idx_records_type` (#3876), on the server only: the primary key
|
|
2235
|
+
* `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
|
|
2236
|
+
* its autoindex (covering, for a count), and the index cost a row on every
|
|
2237
|
+
* insert. Clients' yjs-mode DDL keeps it.
|
|
2195
2238
|
*/
|
|
2196
|
-
static retiredBaseIndexSpecs(
|
|
2239
|
+
static retiredBaseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
|
|
2197
2240
|
/**
|
|
2198
2241
|
* Drop the retired base indexes BY NAME, for the name-based engines (#3688).
|
|
2199
2242
|
*
|
|
@@ -2644,6 +2687,21 @@ declare class DurableObjectEngine extends JsonSchemaEngine {
|
|
|
2644
2687
|
* really is identified by its name.
|
|
2645
2688
|
*/
|
|
2646
2689
|
initializeSchema(): Promise<void>;
|
|
2690
|
+
/**
|
|
2691
|
+
* The index each registration row maps to (#3876): `fieldIndexSpec` per
|
|
2692
|
+
* `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
|
|
2693
|
+
* A row whose `fields_json` does not parse, or whose names are not valid
|
|
2694
|
+
* identifiers, maps to nothing.
|
|
2695
|
+
*/
|
|
2696
|
+
private registeredIndexSpecs;
|
|
2697
|
+
/**
|
|
2698
|
+
* Whether a remaining registration still maps to `spec`'s definition
|
|
2699
|
+
* (#3876). A field index and a unique constraint over the same field — or
|
|
2700
|
+
* two constraints over the same fields — share one physical index since
|
|
2701
|
+
* #3434, so a drop removes it only when no other declaration needs it. The
|
|
2702
|
+
* count is derived from the registration tables at drop time, not stored.
|
|
2703
|
+
*/
|
|
2704
|
+
private definitionStillDeclared;
|
|
2647
2705
|
/**
|
|
2648
2706
|
* Whether this open renamed the pre-underscore column names.
|
|
2649
2707
|
*
|
|
@@ -7059,4 +7117,4 @@ declare class SnapshotPartitioner {
|
|
|
7059
7117
|
private take;
|
|
7060
7118
|
}
|
|
7061
7119
|
|
|
7062
|
-
export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
|
|
7120
|
+
export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, collapseDuplicateIndexes, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, indexDefinitionKey, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
|
package/dist/cloudflare-do.js
CHANGED
|
@@ -2718,6 +2718,11 @@ var DatabaseEngine = class {
|
|
|
2718
2718
|
// src/schema/JsonSchemaDDL.ts
|
|
2719
2719
|
init_sql();
|
|
2720
2720
|
init_sql();
|
|
2721
|
+
var RECORDS_TYPE_INDEX_SPEC = {
|
|
2722
|
+
table: "records",
|
|
2723
|
+
expressions: "_type",
|
|
2724
|
+
canonicalName: "idx_records_type"
|
|
2725
|
+
};
|
|
2721
2726
|
var JsonSchemaDDL = class {
|
|
2722
2727
|
/**
|
|
2723
2728
|
* Generate CREATE TABLE statement for the records table
|
|
@@ -2801,14 +2806,16 @@ CREATE TABLE IF NOT EXISTS stringset_index (
|
|
|
2801
2806
|
* A document whose tables were swapped by a bulk-load ingest carries these
|
|
2802
2807
|
* under session-suffixed names; creating them by name on the next cold open
|
|
2803
2808
|
* would build a second copy of each over the whole table.
|
|
2809
|
+
*
|
|
2810
|
+
* `records(_type)` is a base index only in yjs mode (#3876). The server's
|
|
2811
|
+
* primary key `(_type, _id)` already leads with `_type`, so on a
|
|
2812
|
+
* `DurableObjectEngine` the index served no plan the primary key's autoindex
|
|
2813
|
+
* could not and cost a row on every insert; the yjs-mode list stays in step
|
|
2814
|
+
* with the yjs-mode DDL, which clients keep.
|
|
2804
2815
|
*/
|
|
2805
2816
|
static baseIndexSpecs(options) {
|
|
2806
2817
|
const specs = [
|
|
2807
|
-
{
|
|
2808
|
-
table: "records",
|
|
2809
|
-
expressions: "_type",
|
|
2810
|
-
canonicalName: "idx_records_type"
|
|
2811
|
-
},
|
|
2818
|
+
...options.includeDocIdColumn ? [{ ...RECORDS_TYPE_INDEX_SPEC }] : [],
|
|
2812
2819
|
{
|
|
2813
2820
|
table: "stringset_index",
|
|
2814
2821
|
expressions: "_type, field, value",
|
|
@@ -2837,22 +2844,28 @@ CREATE TABLE IF NOT EXISTS stringset_index (
|
|
|
2837
2844
|
return specs;
|
|
2838
2845
|
}
|
|
2839
2846
|
/**
|
|
2840
|
-
* The base indexes
|
|
2847
|
+
* The base indexes RETIRED, as specs, each removed BY DEFINITION for the
|
|
2848
|
+
* same reason the others are created that way: a document a bulk-load
|
|
2849
|
+
* ingest swapped carries it under a session-suffixed name, which a
|
|
2850
|
+
* `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
|
|
2841
2851
|
*
|
|
2842
|
-
* `(_type, field, _record_id)` is a
|
|
2843
|
-
* autoindex, so it never served a
|
|
2844
|
-
*
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
2852
|
+
* - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
|
|
2853
|
+
* left prefix of the member table's UNIQUE autoindex, so it never served a
|
|
2854
|
+
* plan the autoindex could not; what it did do was cost a second index
|
|
2855
|
+
* write per member insert.
|
|
2856
|
+
* - `idx_records_type` (#3876), on the server only: the primary key
|
|
2857
|
+
* `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
|
|
2858
|
+
* its autoindex (covering, for a count), and the index cost a row on every
|
|
2859
|
+
* insert. Clients' yjs-mode DDL keeps it.
|
|
2848
2860
|
*/
|
|
2849
|
-
static retiredBaseIndexSpecs(
|
|
2861
|
+
static retiredBaseIndexSpecs(options) {
|
|
2850
2862
|
return [
|
|
2851
2863
|
{
|
|
2852
2864
|
table: "stringset_index",
|
|
2853
2865
|
expressions: "_type, field, _record_id",
|
|
2854
2866
|
canonicalName: "idx_ss_type_field_record"
|
|
2855
|
-
}
|
|
2867
|
+
},
|
|
2868
|
+
...options.includeDocIdColumn ? [] : [{ ...RECORDS_TYPE_INDEX_SPEC }]
|
|
2856
2869
|
];
|
|
2857
2870
|
}
|
|
2858
2871
|
/**
|
|
@@ -3298,55 +3311,134 @@ var JsonSchemaEngine = class extends DatabaseEngine {
|
|
|
3298
3311
|
init_sql();
|
|
3299
3312
|
|
|
3300
3313
|
// src/utils/physicalIndex.ts
|
|
3314
|
+
init_sql();
|
|
3301
3315
|
var INDEX_SQL = /^\s*CREATE\s+(UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?"?([A-Za-z0-9_$]+)"?\s+ON\s+"?([A-Za-z0-9_$]+)"?\s*\(([\s\S]*)\)\s*(?:WHERE\s+([\s\S]+))?$/i;
|
|
3316
|
+
var TIGHT = /* @__PURE__ */ new Set(["(", ")", ",", "=", "|"]);
|
|
3317
|
+
function normalizeSqlText(text) {
|
|
3318
|
+
let out = "";
|
|
3319
|
+
let pendingSpace = false;
|
|
3320
|
+
let i = 0;
|
|
3321
|
+
const emit = (chunk) => {
|
|
3322
|
+
const spaced = pendingSpace && out.length > 0 && !TIGHT.has(out[out.length - 1]) && !TIGHT.has(chunk[0]);
|
|
3323
|
+
if (spaced) out += " ";
|
|
3324
|
+
pendingSpace = false;
|
|
3325
|
+
out += chunk;
|
|
3326
|
+
};
|
|
3327
|
+
while (i < text.length) {
|
|
3328
|
+
const ch = text[i];
|
|
3329
|
+
if (ch === "'" || ch === '"') {
|
|
3330
|
+
let j = i + 1;
|
|
3331
|
+
for (; ; ) {
|
|
3332
|
+
if (j >= text.length) break;
|
|
3333
|
+
if (text[j] === ch) {
|
|
3334
|
+
if (text[j + 1] === ch) {
|
|
3335
|
+
j += 2;
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
3338
|
+
break;
|
|
3339
|
+
}
|
|
3340
|
+
j++;
|
|
3341
|
+
}
|
|
3342
|
+
emit(text.slice(i, j + 1));
|
|
3343
|
+
i = j + 1;
|
|
3344
|
+
continue;
|
|
3345
|
+
}
|
|
3346
|
+
if (/\s/.test(ch)) {
|
|
3347
|
+
pendingSpace = true;
|
|
3348
|
+
i++;
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3351
|
+
emit(ch);
|
|
3352
|
+
i++;
|
|
3353
|
+
}
|
|
3354
|
+
return out;
|
|
3355
|
+
}
|
|
3302
3356
|
function shapeOf(options) {
|
|
3303
|
-
const strip = (text) => text.replace(/\s+/g, "");
|
|
3304
3357
|
return [
|
|
3305
3358
|
options.unique ? "UNIQUE" : "INDEX",
|
|
3306
|
-
|
|
3307
|
-
options.where ?
|
|
3308
|
-
].join("
|
|
3359
|
+
normalizeSqlText(options.expressions),
|
|
3360
|
+
options.where ? normalizeSqlText(options.where) : ""
|
|
3361
|
+
].join(" ");
|
|
3309
3362
|
}
|
|
3310
|
-
function
|
|
3363
|
+
function indexDefinitionKey(spec) {
|
|
3311
3364
|
return shapeOf({
|
|
3312
3365
|
unique: Boolean(spec.unique),
|
|
3313
3366
|
expressions: spec.expressions,
|
|
3314
3367
|
where: spec.where ?? null
|
|
3315
3368
|
});
|
|
3316
3369
|
}
|
|
3317
|
-
function
|
|
3318
|
-
const wanted = shapeOfSpec(spec);
|
|
3370
|
+
function namedIndexesOf(exec, table) {
|
|
3319
3371
|
const rows = exec(
|
|
3320
3372
|
`SELECT name, sql FROM sqlite_master
|
|
3321
|
-
WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
|
|
3322
|
-
|
|
3373
|
+
WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
|
|
3374
|
+
ORDER BY name`,
|
|
3375
|
+
[table]
|
|
3323
3376
|
);
|
|
3377
|
+
const named = [];
|
|
3324
3378
|
for (const row of rows) {
|
|
3325
3379
|
const match = INDEX_SQL.exec(String(row.sql));
|
|
3326
3380
|
if (!match) continue;
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3381
|
+
named.push({
|
|
3382
|
+
name: String(row.name),
|
|
3383
|
+
key: shapeOf({
|
|
3384
|
+
unique: Boolean(match[1]),
|
|
3385
|
+
expressions: match[4] ?? "",
|
|
3386
|
+
where: match[5] ?? null
|
|
3387
|
+
})
|
|
3331
3388
|
});
|
|
3332
|
-
if (shape === wanted) return String(row.name);
|
|
3333
3389
|
}
|
|
3334
|
-
return
|
|
3390
|
+
return named;
|
|
3391
|
+
}
|
|
3392
|
+
function resolvePhysicalIndex(exec, spec) {
|
|
3393
|
+
const wanted = indexDefinitionKey(spec);
|
|
3394
|
+
const found = namedIndexesOf(exec, spec.table).find(
|
|
3395
|
+
(index) => index.key === wanted
|
|
3396
|
+
);
|
|
3397
|
+
return found ? found.name : null;
|
|
3398
|
+
}
|
|
3399
|
+
function unusedIndexName(exec, canonicalName) {
|
|
3400
|
+
const taken = (name) => exec("SELECT 1 FROM sqlite_master WHERE name = ? COLLATE NOCASE", [name]).length > 0;
|
|
3401
|
+
if (!taken(canonicalName)) return canonicalName;
|
|
3402
|
+
for (let n = 2; ; n++) {
|
|
3403
|
+
const candidate = `${canonicalName}_${n}`;
|
|
3404
|
+
if (!taken(candidate)) return candidate;
|
|
3405
|
+
}
|
|
3335
3406
|
}
|
|
3336
3407
|
function ensurePhysicalIndex(exec, spec) {
|
|
3337
3408
|
if (resolvePhysicalIndex(exec, spec) !== null) return "exists";
|
|
3338
3409
|
exec(
|
|
3339
|
-
`CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${spec.canonicalName} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
|
|
3410
|
+
`CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${unusedIndexName(exec, spec.canonicalName)} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
|
|
3340
3411
|
);
|
|
3341
3412
|
return "created";
|
|
3342
3413
|
}
|
|
3343
3414
|
function removePhysicalIndex(exec, spec) {
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3415
|
+
const wanted = indexDefinitionKey(spec);
|
|
3416
|
+
for (const index of namedIndexesOf(exec, spec.table)) {
|
|
3417
|
+
if (index.key !== wanted) continue;
|
|
3418
|
+
exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
function collapseDuplicateIndexes(exec, table, ownedKeys) {
|
|
3422
|
+
const kept = /* @__PURE__ */ new Set();
|
|
3423
|
+
const dropped = [];
|
|
3424
|
+
for (const index of namedIndexesOf(exec, table)) {
|
|
3425
|
+
if (!ownedKeys.has(index.key)) continue;
|
|
3426
|
+
if (!kept.has(index.key)) {
|
|
3427
|
+
kept.add(index.key);
|
|
3428
|
+
continue;
|
|
3429
|
+
}
|
|
3430
|
+
exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
|
|
3431
|
+
dropped.push(index.name);
|
|
3432
|
+
}
|
|
3433
|
+
return dropped;
|
|
3347
3434
|
}
|
|
3348
3435
|
|
|
3349
3436
|
// src/engines/cloudflare/DurableObjectEngine.ts
|
|
3437
|
+
function ownedDefinitionKeys(specs, table) {
|
|
3438
|
+
return new Set(
|
|
3439
|
+
specs.filter((spec) => spec.table === table).map(indexDefinitionKey)
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3350
3442
|
var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
3351
3443
|
sql;
|
|
3352
3444
|
storage;
|
|
@@ -3399,8 +3491,85 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
3399
3491
|
for (const spec of JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions)) {
|
|
3400
3492
|
removePhysicalIndex(this.indexExec, spec);
|
|
3401
3493
|
}
|
|
3494
|
+
const owned = [
|
|
3495
|
+
...JsonSchemaDDL.baseIndexSpecs(this.schemaOptions),
|
|
3496
|
+
...JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions),
|
|
3497
|
+
...this.registeredIndexSpecs()
|
|
3498
|
+
];
|
|
3499
|
+
for (const table of ["records", "stringset_index"]) {
|
|
3500
|
+
const dropped = collapseDuplicateIndexes(
|
|
3501
|
+
this.indexExec,
|
|
3502
|
+
table,
|
|
3503
|
+
ownedDefinitionKeys(owned, table)
|
|
3504
|
+
);
|
|
3505
|
+
if (dropped.length > 0) {
|
|
3506
|
+
console.info("Durable Object engine collapsed duplicate indexes", {
|
|
3507
|
+
table,
|
|
3508
|
+
dropped
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3402
3512
|
this.initialized = true;
|
|
3403
3513
|
}
|
|
3514
|
+
/**
|
|
3515
|
+
* The index each registration row maps to (#3876): `fieldIndexSpec` per
|
|
3516
|
+
* `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
|
|
3517
|
+
* A row whose `fields_json` does not parse, or whose names are not valid
|
|
3518
|
+
* identifiers, maps to nothing.
|
|
3519
|
+
*/
|
|
3520
|
+
registeredIndexSpecs() {
|
|
3521
|
+
const specs = [];
|
|
3522
|
+
try {
|
|
3523
|
+
for (const row of this.execSqlSync(
|
|
3524
|
+
"SELECT model_name, field_name FROM _indexes"
|
|
3525
|
+
)) {
|
|
3526
|
+
try {
|
|
3527
|
+
specs.push(
|
|
3528
|
+
JsonSchemaDDL.fieldIndexSpec(
|
|
3529
|
+
String(row.model_name),
|
|
3530
|
+
String(row.field_name)
|
|
3531
|
+
)
|
|
3532
|
+
);
|
|
3533
|
+
} catch (e) {
|
|
3534
|
+
console.debug("Skipping an unusable index registration:", e);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
} catch (e) {
|
|
3538
|
+
console.debug("Could not read index registrations:", e);
|
|
3539
|
+
}
|
|
3540
|
+
try {
|
|
3541
|
+
for (const row of this.execSqlSync(
|
|
3542
|
+
"SELECT model_name, constraint_name, fields_json FROM _unique_constraints"
|
|
3543
|
+
)) {
|
|
3544
|
+
try {
|
|
3545
|
+
specs.push(
|
|
3546
|
+
JsonSchemaDDL.uniqueConstraintIndexSpec(
|
|
3547
|
+
String(row.model_name),
|
|
3548
|
+
String(row.constraint_name),
|
|
3549
|
+
JSON.parse(String(row.fields_json))
|
|
3550
|
+
)
|
|
3551
|
+
);
|
|
3552
|
+
} catch (e) {
|
|
3553
|
+
console.debug("Skipping an unusable unique-constraint registration:", e);
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
} catch (e) {
|
|
3557
|
+
console.debug("Could not read unique-constraint registrations:", e);
|
|
3558
|
+
}
|
|
3559
|
+
return specs;
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Whether a remaining registration still maps to `spec`'s definition
|
|
3563
|
+
* (#3876). A field index and a unique constraint over the same field — or
|
|
3564
|
+
* two constraints over the same fields — share one physical index since
|
|
3565
|
+
* #3434, so a drop removes it only when no other declaration needs it. The
|
|
3566
|
+
* count is derived from the registration tables at drop time, not stored.
|
|
3567
|
+
*/
|
|
3568
|
+
definitionStillDeclared(spec) {
|
|
3569
|
+
return ownedDefinitionKeys(this.registeredIndexSpecs(), spec.table).has(
|
|
3570
|
+
indexDefinitionKey(spec)
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3404
3573
|
/**
|
|
3405
3574
|
* Whether this open renamed the pre-underscore column names.
|
|
3406
3575
|
*
|
|
@@ -3556,10 +3725,9 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
3556
3725
|
"DELETE FROM _indexes WHERE model_name = ? AND field_name = ?",
|
|
3557
3726
|
[modelName, fieldName]
|
|
3558
3727
|
);
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
);
|
|
3728
|
+
const spec = JsonSchemaDDL.fieldIndexSpec(modelName, fieldName);
|
|
3729
|
+
if (this.definitionStillDeclared(spec)) return;
|
|
3730
|
+
removePhysicalIndex(this.indexExec, spec);
|
|
3563
3731
|
}
|
|
3564
3732
|
/**
|
|
3565
3733
|
* List all registered indexes, optionally filtered by model.
|
|
@@ -3624,10 +3792,13 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
|
|
|
3624
3792
|
} catch {
|
|
3625
3793
|
return;
|
|
3626
3794
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3795
|
+
const spec = JsonSchemaDDL.uniqueConstraintIndexSpec(
|
|
3796
|
+
modelName,
|
|
3797
|
+
constraintName,
|
|
3798
|
+
fields
|
|
3630
3799
|
);
|
|
3800
|
+
if (this.definitionStillDeclared(spec)) return;
|
|
3801
|
+
removePhysicalIndex(this.indexExec, spec);
|
|
3631
3802
|
}
|
|
3632
3803
|
/**
|
|
3633
3804
|
* List composite unique constraints, optionally filtered by model.
|
|
@@ -7969,6 +8140,7 @@ export {
|
|
|
7969
8140
|
checkAutoStampsReservedFields,
|
|
7970
8141
|
checkReservedFieldName,
|
|
7971
8142
|
clearMetaSyncCache,
|
|
8143
|
+
collapseDuplicateIndexes,
|
|
7972
8144
|
compareRecordIds,
|
|
7973
8145
|
completeOverlayEntries,
|
|
7974
8146
|
createDatabaseDO,
|
|
@@ -7988,6 +8160,7 @@ export {
|
|
|
7988
8160
|
format2StringSetMembers,
|
|
7989
8161
|
groupOverlayEntries,
|
|
7990
8162
|
handleRequest,
|
|
8163
|
+
indexDefinitionKey,
|
|
7991
8164
|
inferFieldType,
|
|
7992
8165
|
isAnchorId,
|
|
7993
8166
|
isFormat2StringSetYMap,
|
package/package.json
CHANGED