Skip to content

Commit

Permalink
Support multiple indexes for same hash (#333)
Browse files Browse the repository at this point in the history
  • Loading branch information
krumft authored Sep 30, 2023
1 parent 42ca008 commit f784626
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 31 deletions.
84 changes: 53 additions & 31 deletions src/table.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IMemoryTable, Schema, QueryError, TableEvent, PermissionDeniedError, NotSupported, IndexDef, ColumnNotFound, ISubscription, nil, DataType } from './interfaces';
import { _ISelection, IValue, _ITable, setId, getId, CreateIndexDef, CreateIndexColDef, _IDb, _Transaction, _ISchema, _Column, _IType, SchemaField, _IIndex, _Explainer, _SelectExplanation, ChangeHandler, Stats, OnConflictHandler, DropHandler, IndexHandler, asIndex, RegClass, RegType, Reg, ChangeOpts, _IConstraint, TruncateHandler, TruncateOpts } from './interfaces-private';
import { IMemoryTable, Schema, QueryError, TableEvent, PermissionDeniedError, NotSupported, IndexDef, ISubscription, nil } from './interfaces';
import { IValue, _ITable, setId, getId, CreateIndexDef, CreateIndexColDef, _Transaction, _ISchema, _Column, _IType, SchemaField, _IIndex, _Explainer, _SelectExplanation, ChangeHandler, Stats, DropHandler, IndexHandler, asIndex, Reg, ChangeOpts, _IConstraint, TruncateHandler, TruncateOpts } from './interfaces-private';
import { buildValue } from './parser/expression-builder';
import { BIndex } from './schema/btree-index';
import { columnEvaluator } from './transforms/selection';
Expand Down Expand Up @@ -84,10 +84,10 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
private dataId = Symbol();
private serialsId: symbol = Symbol();
private constraintsByName = new Map<string, _IConstraint>();
private indexByHash = new Map<string, {
private indexByHashAndName = new Map<string, Map<string, {
index: BIndex<T>;
expressions: IValue[];
}>();
}>>();
readonly columnMgr = new ColumnManager();
name: string;

Expand Down Expand Up @@ -329,14 +329,16 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
if (onConflict) {
if ('ignore' in onConflict) {
if (onConflict.ignore === 'all') {
for (const k of this.indexByHash.values()) {
if (!k.index.unique) {
continue;
}
const key = k.index.buildKey(toInsert, t);
const found = k.index.eqFirst(key, t);
if (found) {
return found; // ignore.
for (const map of this.indexByHashAndName.values()) {
for (const k of map.values()) {
if (!k.index.unique) {
continue;
}
const key = k.index.buildKey(toInsert, t);
const found = k.index.eqFirst(key, t);
if (found) {
return found; // ignore.
}
}
}
} else {
Expand Down Expand Up @@ -454,8 +456,10 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa

// remove old version from index
if (exists) {
for (const k of this.indexByHash.values()) {
k.index.delete(exists, t);
for (const map of this.indexByHashAndName.values()) {
for (const k of map.values()) {
k.index.delete(exists, t);
}
}
}

Expand All @@ -481,8 +485,10 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
changePlan.after();

// remove from indices
for (const k of this.indexByHash.values()) {
k.index.delete(got, t);
for (const map of this.indexByHashAndName.values()) {
for (const k of map.values()) {
k.index.delete(got, t);
}
}
this.setBin(t, bin.delete(id));

Expand All @@ -496,16 +502,20 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
h(t, opts);
}
// truncate indices
for (const k of this.indexByHash.values()) {
k.index.truncate(t);
for (const map of this.indexByHashAndName.values()) {
for (const k of map.values()) {
k.index.truncate(t);
}
}
this.setBin(t, ImMap());
}


private indexElt(t: _Transaction, toInsert: T) {
for (const k of this.indexByHash.values()) {
k.index.add(toInsert, t);
for (const map of this.indexByHashAndName.values()) {
for (const k of map.values()) {
k.index.add(toInsert, t);
}
}
}

Expand All @@ -522,7 +532,8 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
if (this.hasPrimary?.hash === ihash) {
return this.hasPrimary;
}
const got = this.indexByHash.get(ihash);
const map = this.indexByHashAndName.get(ihash);
const got = map?.values().next().value;
return got?.index ?? null;
}

Expand Down Expand Up @@ -632,7 +643,9 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
this.getColumnRef(used.id!).usedInIndexes.add(index);
}
}
this.indexByHash.set(ihash, { index, expressions: index.expressions });
const indexesByHash = this.indexByHashAndName.get(ihash) || new Map<string, {index: BIndex<T>; expressions: IValue[]}>();
indexesByHash.set(indexName, { index, expressions: index.expressions });
this.indexByHashAndName.set(ihash, indexesByHash);
if (expressions.primary) {
this.hasPrimary = index;
}
Expand Down Expand Up @@ -662,11 +675,14 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa

dropIndex(t: _Transaction, uName: string) {
const u = asIndex(this.ownerSchema.getOwnObject(uName)) as BIndex;
if (!u || !this.indexByHash.has(u.hash)) {
if (!u || !this.indexByHashAndName.has(u.hash) || !this.indexByHashAndName.get(u.hash)?.has(uName)) {
throw new QueryError('Cannot drop index that does not belong to this table: ' + uName);
}
this.indexHandlers.forEach(h => h('drop', u));
this.indexByHash.delete(u.hash);
this.indexByHashAndName.get(u.hash)?.delete(uName);
if (this.indexByHashAndName.get(u.hash)?.size == 0) {
this.indexByHashAndName.delete(u.hash);
}
u.dropFromData(t);
this.ownerSchema._reg_unregister(u);
this.constraintsByName.delete(uName);
Expand All @@ -689,11 +705,15 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
}

listIndices(): IndexDef[] {
return [...this.indexByHash.values()]
.map<IndexDef>(x => ({
name: x.index.name!,
expressions: x.expressions.map(x => x.id!),
}));
return ([] as IndexDef[]).concat(...
[...this.indexByHashAndName.values()]
.map(indexMap => [...indexMap.values()]
.map<IndexDef>(x => ({
name: x.index.name!,
expressions: x.expressions.map(x => x.id!)
}))
)
);
}

addForeignKey(cst: TableConstraintForeignKey, t: _Transaction): _IConstraint | nil {
Expand Down Expand Up @@ -776,8 +796,10 @@ export class MemoryTable<T = any> extends DataSourceBase<T> implements IMemoryTa
drop(t: _Transaction, cascade: boolean) {
this.drophandlers.forEach(d => d(t, cascade));
t.delete(this.dataId);
for (const i of this.indexByHash.values()) {
i.index.dropFromData(t);
for (const map of this.indexByHashAndName.values()) {
for (const i of map.values()) {
i.index.dropFromData(t);
}
}
// todo should also check foreign keys, cascade, ...
return this.ownerSchema._reg_unregister(this);
Expand Down
9 changes: 9 additions & 0 deletions src/tests/constraints.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ describe('Constraints', () => {
`);
});

it('can create and drop a constraint and index on the same column', () => {
none(`create table test (col text);
alter table test add constraint uq unique (col);
create index idx on test(col);
drop index idx;
alter table test drop constraint uq;
`);
});

it('can drop an index via drop constraint, and then drop the column', () => {
none(`create table test(id text, col text);
alter table test add constraint abc unique (col);
Expand Down

0 comments on commit f784626

Please sign in to comment.