Skip to content

Commit

Permalink
feat: implements Sqlite 'WITHOUT ROWID' table modifier (#4688)
Browse files Browse the repository at this point in the history
* feat: implements Sqlite 'WITHOUT ROWID' table modifier

This new feature adds a 'withoutRowid' option to EntityOptions in order to
enable Sqlite 'WITHOUT ROWID' modifier to the 'CREATE TABLE' statement.
See https://www.sqlite.org/withoutrowid.html

Closes: #3330

* style: adding semicolon and space complained by linter

adding semicolons and spaces complained by linter
  • Loading branch information
bnegrao authored and pleerock committed Oct 18, 2019
1 parent 2f27581 commit c1342ad
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/decorator/entity/Entity.ts
Expand Up @@ -30,7 +30,8 @@ export function Entity(nameOrOptions?: string|EntityOptions, maybeOptions?: Enti
engine: options.engine ? options.engine : undefined,
database: options.database ? options.database : undefined,
schema: options.schema ? options.schema : undefined,
synchronize: options.synchronize
synchronize: options.synchronize,
withoutRowid: options.withoutRowid
} as TableMetadataArgs);
};
}
7 changes: 7 additions & 0 deletions src/decorator/options/EntityOptions.ts
Expand Up @@ -40,4 +40,11 @@ export interface EntityOptions {
* By default schema synchronization is enabled for all entities.
*/
synchronize?: boolean;

/**
* If set to 'true' this option disables Sqlite's default behaviour of secretly creating
* an integer primary key column named 'rowid' on table creation.
* @see https://www.sqlite.org/withoutrowid.html.
*/
withoutRowid?: boolean;
}
5 changes: 5 additions & 0 deletions src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts
Expand Up @@ -997,6 +997,11 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen

sql += `)`;

const tableMetadata = this.connection.entityMetadatas.find(metadata => metadata.tableName === table.name);
if (tableMetadata && tableMetadata.withoutRowid) {
sql += " WITHOUT ROWID";
}

return new Query(sql);
}

Expand Down
6 changes: 5 additions & 1 deletion src/metadata-args/TableMetadataArgs.ts
Expand Up @@ -59,7 +59,11 @@ export interface TableMetadataArgs {
/**
* Indicates if view is materialized
*/

materialized?: boolean;

/**
* If set to 'true' this option disables Sqlite's default behaviour of secretly creating
* an integer primary key column named 'rowid' on table creation.
*/
withoutRowid?: boolean;
}
7 changes: 7 additions & 0 deletions src/metadata/EntityMetadata.ts
Expand Up @@ -101,6 +101,11 @@ export class EntityMetadata {
*/
expression?: string|((connection: Connection) => SelectQueryBuilder<any>);

/**
* Enables Sqlite "WITHOUT ROWID" modifier for the "CREATE TABLE" statement
*/
withoutRowid?: boolean = false;

/**
* Original user-given table name (taken from schema or @Entity(tableName) decorator).
* If user haven't specified a table name this property will be undefined.
Expand Down Expand Up @@ -496,6 +501,7 @@ export class EntityMetadata {
this.target = this.tableMetadataArgs.target;
this.tableType = this.tableMetadataArgs.type;
this.expression = this.tableMetadataArgs.expression;
this.withoutRowid = this.tableMetadataArgs.withoutRowid;
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -794,6 +800,7 @@ export class EntityMetadata {
this.target = this.target ? this.target : this.tableName;
this.name = this.targetName ? this.targetName : this.tableName;
this.expression = this.tableMetadataArgs.expression;
this.withoutRowid = this.tableMetadataArgs.withoutRowid === true ? true : false;
this.tablePath = this.buildTablePath();
this.schemaPath = this.buildSchemaPath();
this.orderBy = (this.tableMetadataArgs.orderBy instanceof Function) ? this.tableMetadataArgs.orderBy(this.propertiesMap) : this.tableMetadataArgs.orderBy; // todo: is propertiesMap available here? Looks like its not
Expand Down
41 changes: 41 additions & 0 deletions test/functional/query-runner/create-table.ts
Expand Up @@ -10,6 +10,8 @@ import {MysqlDriver} from "../../../src/driver/mysql/MysqlDriver";
import {AbstractSqliteDriver} from "../../../src/driver/sqlite-abstract/AbstractSqliteDriver";
import {OracleDriver} from "../../../src/driver/oracle/OracleDriver";
import {Photo} from "./entity/Photo";
import {Book2, Book} from "./entity/Book";
import {SqliteDriver} from "../../../src/driver/sqlite/SqliteDriver";

describe("query runner > create table", () => {

Expand Down Expand Up @@ -327,4 +329,43 @@ describe("query runner > create table", () => {
await queryRunner.release();
})));

it("should correctly create table with different `withoutRowid` definitions", () => Promise.all(connections.map(async connection => {

if (connection.driver instanceof SqliteDriver) {
const queryRunner = connection.createQueryRunner();

// the table 'book' must contain a 'rowid' column
const metadataBook = connection.getMetadata(Book);
const newTableBook = Table.create(metadataBook, connection.driver);
await queryRunner.createTable(newTableBook);
const aBook = new Book();
aBook.ean = "asdf";
await connection.manager.save(aBook);

const desc = await connection.manager.query("SELECT rowid FROM book WHERE ean = 'asdf'");
expect(desc[0].rowid).equals(1);

await queryRunner.dropTable("book");
const bookTableIsGone = await queryRunner.getTable("book");
expect(bookTableIsGone).to.be.undefined;

// the table 'book2' must NOT contain a 'rowid' column
const metadataBook2 = connection.getMetadata(Book2);
const newTableBook2 = Table.create(metadataBook2, connection.driver);
await queryRunner.createTable(newTableBook2);

try {
await connection.manager.query("SELECT rowid FROM book2");
} catch (e) {
expect(e.message).equal("SQLITE_ERROR: no such column: rowid");
}

await queryRunner.dropTable("book2");
const book2TableIsGone = await queryRunner.getTable("book2");
expect(book2TableIsGone).to.be.undefined;

await queryRunner.release();
}
})));

});
19 changes: 19 additions & 0 deletions test/functional/query-runner/entity/Book.ts
@@ -0,0 +1,19 @@
import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn";
import { Entity } from "../../../../src/decorator/entity/Entity";

@Entity()
export class Book {

@PrimaryColumn()
ean: string;

}

@Entity({ withoutRowid: true })
export class Book2 {

@PrimaryColumn()
ean: string;

}

0 comments on commit c1342ad

Please sign in to comment.