Merge pull request #2853 from beekeeper-studio/rc-51

Rc 51 merge back into master
This commit is contained in:
Matthew Rathbone
2025-03-12 17:07:21 -05:00
committed by GitHub
8 changed files with 93 additions and 14 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "beekeeper-studio",
"version": "5.1.4",
"version": "5.1.5",
"private": true,
"description": "An easy-to use SQL query editor and database UI for Mac, Windows, and Linux",
"author": {

View File

@@ -522,7 +522,7 @@ export class DuckDBClient extends BasicDatabaseClient<DuckDBResult> {
return rows.map((row) => row.schema_name as string);
}
async getTableReferences(_table: string, _schema: string): Promise<string[]> {
async getTableReferences(table: string, schema: string): Promise<string[]> {
const { rows } = await this.driverExecuteSingle(`
WITH cte AS (
SELECT rc.unique_constraint_name AS unique_constraint_name
@@ -532,14 +532,14 @@ export class DuckDBClient extends BasicDatabaseClient<DuckDBResult> {
JOIN information_schema.table_constraints tc
ON rc.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = 'main'
AND tc.table_name = 'dept_emp'
AND tc.table_schema = ?
AND tc.table_name = ?
)
SELECT kc.table_name
FROM cte
JOIN information_schema.key_column_usage kc
ON cte.unique_constraint_name = kc.constraint_name
`);
`, { params: [schema || await this.defaultSchema(), table] });
return rows.map((row) => row.table_name as string);
}
@@ -1062,8 +1062,6 @@ export class DuckDBClient extends BasicDatabaseClient<DuckDBResult> {
// took this approach because Typescript wasn't liking the base function could be a null value or a function
createUpsertSQL({ name: tableName }: DatabaseEntity, data: {[key: string]: any}[]): string {
const [firstObj] = data
console.log('~~~ what is that data yo? ~~~')
console.log(data)
const columns = Object.keys(firstObj)
const values = data.map(d => `(${columns.map(c => `'${d[c]}'`).join()})`).join()
return `INSERT OR REPLACE \`${tableName}\`, (${columns.map(cpk => `\`${cpk}\``).join(', ')}) VALUES ${values}`.trim()

View File

@@ -312,7 +312,7 @@ export class SavedConnection extends DbConnectionBase implements IConnection {
if(options.find((e) => url.endsWith(e))) {
this.connectionType = connectionType as any
this.defaultDatabase = url
return
return true;
}
}
}

View File

@@ -15,7 +15,7 @@ export interface Version {
* ```
**/
export function parseVersion(version: string) {
const versionTagRegex = /^(\d+)\.(\d+)\.(\d+)$/;
const versionTagRegex = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
const match = versionTagRegex.exec(version) || [];
const [major, minor, patch] = _.tail(match).map((x) => parseInt(x));
return { major, minor, patch };
@@ -23,9 +23,14 @@ export function parseVersion(version: string) {
/** Check if version a is less than or equal to version b */
export function isVersionLessThanOrEqual(a: Version, b: Version) {
if (a.major > b.major) return false;
if (a.minor > b.minor) return false;
if (a.patch > b.patch) return false;
return true;
if (a.major > b.major) return false
if (a.major === b.major) {
if (a.minor > b.minor) return false;
if (a.minor === b.minor && a.patch > b.patch) return false
return true
} else { // a.major < b.major
return true
}
}

View File

@@ -10,6 +10,7 @@
</template>
<script lang="ts">
import "codemirror/addon/display/autorefresh";
import "codemirror/addon/comment/comment";
import "codemirror/addon/dialog/dialog";
import "codemirror/addon/search/search";
@@ -207,6 +208,7 @@ export default {
indicatorFolded.innerHTML = `<i class="dropdown-icon material-icons">keyboard_arrow_right</i>`;
const cm = CodeMirror.fromTextArea(this.$refs.editor, {
autoRefresh: true,
lineNumbers: this.lineNumbers ?? true,
tabSize: 2,
theme: "monokai",

View File

@@ -120,8 +120,10 @@ export const LicenseModule: Module<State, RootState> = {
await context.dispatch('sync')
},
async update(_context, license: TransportLicenseKey) {
// This is to allow for dev switching
const isDevUpdate = window.platformInfo.isDevelopment && license.email == "fake_email";
try {
const data = await CloudClient.getLicense(window.platformInfo.cloudUrl, license.email, license.key)
const data = isDevUpdate ? license : await CloudClient.getLicense(window.platformInfo.cloudUrl, license.email, license.key)
license.validUntil = new Date(data.validUntil)
license.supportUntil = new Date(data.supportUntil)
license.maxAllowedAppRelease = data.maxAllowedAppRelease

View File

@@ -140,6 +140,64 @@ function testWith(options: typeof TEST_VERSIONS[number]) {
await util.knex.schema.raw("DROP INDEX col_exp_test_idx");
});
});
describe("Table References Tests", () => {
beforeAll(async () => {
// Create parent and child tables with foreign key relationships
await util.knex.schema.raw(`
CREATE TABLE departments (
department_id INT PRIMARY KEY,
department_name VARCHAR(100)
)
`);
await util.knex.schema.raw(`
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
department_id INT,
employee_name VARCHAR(100),
FOREIGN KEY (department_id) REFERENCES departments(department_id)
)
`);
});
afterAll(async () => {
await util.knex.schema.dropTableIfExists("employees");
await util.knex.schema.dropTableIfExists("departments");
});
it("should get table references correctly", async () => {
// This test may be skipped since it depends on DuckDB's capabilities
// The implementation returns empty results if not supported
try {
const references = await util.connection.getTableReferences("departments", "main");
expect(Array.isArray(references)).toBe(true);
// Note: DuckDB may not fully support foreign key constraints in a way that
// allows listing references, so the actual result may be empty
} catch (e) {
console.warn("DuckDB getTableReferences test skipped due to DuckDB limitations");
}
});
});
describe("Upsert SQL Tests", () => {
it("should create correct upsert SQL", async () => {
const entity = { name: "test_table" };
const data = [
{ id: 1, name: "Test 1" },
{ id: 2, name: "Test 2" }
];
// Access the client directly to test the createUpsertSQL method
// @ts-ignore - Accessing private method for testing
const sql = util.connection.createUpsertSQL(entity, data);
expect(sql).toContain("INSERT OR REPLACE");
expect(sql).toContain("`test_table`");
expect(sql).toContain("`id`, `name`");
expect(sql).toContain("('1','Test 1'),('2','Test 2')");
});
});
});
}

View File

@@ -67,6 +67,11 @@ describe("License", () => {
expect(isVersionLessThanOrEqual(v`1.2.1`, v`1.2.3`)).toBe(true);
expect(isVersionLessThanOrEqual(v`1.2.3`, v`1.0.0`)).toBe(false);
expect(isVersionLessThanOrEqual(v`1.3.2`, v`1.2.3`)).toBe(false);
expect(isVersionLessThanOrEqual(v`1.99.99`, v`2.2.3`)).toBe(true);
expect(isVersionLessThanOrEqual(v`1.1.99`, v`1.2.3`)).toBe(true);
expect(isVersionLessThanOrEqual(v`1.1.99-beta.1`, v`1.1.99-beta.2`)).toBe(true);
expect(isVersionLessThanOrEqual(v`1.1.99`, v`1.2.0-beta.1`)).toBe(true);
});
describe("License status", () => {
@@ -163,5 +168,14 @@ describe("License", () => {
condition: ["Expired support date", "App version not allowed"],
});
})
// Regression tests for version comparison
it("Should properly compare versions", async () => {
expect(isVersionLessThanOrEqual(parseVersion("v2.4.6"), parseVersion("v5.7.2"))).toBeTruthy();
expect(isVersionLessThanOrEqual(parseVersion("v2.5.1-beta.4"), parseVersion("v5.0.0"))).toBeTruthy();
expect(isVersionLessThanOrEqual(parseVersion("v6.3.7"), parseVersion("v4.2.1"))).not.toBeTruthy;
expect(isVersionLessThanOrEqual(parseVersion("v3.1.3-beta.4"), parseVersion("v1.8.1"))).not.toBeTruthy();
expect(isVersionLessThanOrEqual(parseVersion("v5.0.0"), parseVersion("v5.0.0"))).toBeTruthy();
})
});
});