diff --git a/packages/language-support/src/index.ts b/packages/language-support/src/index.ts index c4ab87def..6fcf0c959 100644 --- a/packages/language-support/src/index.ts +++ b/packages/language-support/src/index.ts @@ -24,14 +24,13 @@ export { lintCypherQuery } from './syntaxValidation/syntaxValidation'; export type { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation'; export { testData } from './tests/testData'; export { textMateGrammar } from './textMateGrammar'; -export { cypherVersions } from './types'; +export { allCypherVersions } from './types'; export type { CompletionItem, CypherVersion, Neo4jFunction, Neo4jProcedure, } from './types'; -export { cypher25Supported } from './version'; export { CypherLexer, CypherParser, CypherParserListener, CypherParserVisitor }; import CypherLexer from './generated-parser/CypherCmdLexer'; diff --git a/packages/language-support/src/parserWrapper.ts b/packages/language-support/src/parserWrapper.ts index 3373d7ad8..8b35ed4e9 100644 --- a/packages/language-support/src/parserWrapper.ts +++ b/packages/language-support/src/parserWrapper.ts @@ -31,7 +31,11 @@ import { } from './helpers'; import { SyntaxDiagnostic } from './syntaxValidation/syntaxValidation'; import { SyntaxErrorsListener } from './syntaxValidation/syntaxValidationHelpers'; -import { CypherVersion, cypherVersionNumbers, cypherVersions } from './types'; +import { + CypherVersion, + cypherVersionNumbers, + allCypherVersions, +} from './types'; export interface ParsedStatement { command: ParsedCommand; @@ -494,7 +498,7 @@ class CypherVersionCollector extends ParseTreeListener { exitEveryRule(ctx: unknown) { if (ctx instanceof CypherVersionContext) { const parsedVersion = 'CYPHER ' + ctx.getText(); - cypherVersions.forEach((validVersion) => { + allCypherVersions.forEach((validVersion) => { if (parsedVersion === validVersion) { this.cypherVersion = parsedVersion; } diff --git a/packages/language-support/src/tests/version.test.ts b/packages/language-support/src/tests/version.test.ts deleted file mode 100644 index 589526424..000000000 --- a/packages/language-support/src/tests/version.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { cypher25Supported } from '../version'; - -describe('version tests', () => { - test('cypher 25 should not be supported in 5.27.0', () => { - expect(cypher25Supported('5.27.0')).toBe(false); - }); - - test('cypher 25 should not be supported in 5.27.0-2025030', () => { - expect(cypher25Supported('5.27.0-2025030')).toBe(false); - }); - - test('cypher 25 should be supported 5.27.0-2025040 onwards', () => { - expect(cypher25Supported('5.27.0-2025040')).toBe(true); - expect(cypher25Supported('5.27.0-2025050')).toBe(true); - expect(cypher25Supported('5.27.0-2025060')).toBe(true); - expect(cypher25Supported('5.28')).toBe(true); - expect(cypher25Supported('5.28.0')).toBe(true); - expect(cypher25Supported('6.1.0')).toBe(true); - expect(cypher25Supported('5.27.0-beta')).toBe(false); - expect(cypher25Supported('5.27.0-alpha.2025050')).toBe(false); - expect(cypher25Supported('6.1.0-preRelease.3')).toBe(true); - }); -}); diff --git a/packages/language-support/src/types.ts b/packages/language-support/src/types.ts index 512e5e937..0844f204b 100644 --- a/packages/language-support/src/types.ts +++ b/packages/language-support/src/types.ts @@ -7,9 +7,9 @@ export type ReturnDescription = { isDeprecated: boolean; }; -export const cypherVersions = ['CYPHER 5', 'CYPHER 25']; +export const allCypherVersions = ['CYPHER 5', 'CYPHER 25']; export const cypherVersionNumbers: string[] = ['5', '25']; -export type CypherVersion = (typeof cypherVersions)[number]; +export type CypherVersion = (typeof allCypherVersions)[number]; // we could parse this string for better types in the future export type Neo4jStringType = string; diff --git a/packages/language-support/src/version.ts b/packages/language-support/src/version.ts deleted file mode 100644 index 5400f6e0d..000000000 --- a/packages/language-support/src/version.ts +++ /dev/null @@ -1,27 +0,0 @@ -import semver from 'semver'; - -export function cypher25Supported(serverVersion: string) { - const minSupportedVersion = '5.27.0-2025040'; - const minSupported = semver.coerce(minSupportedVersion, { - includePrerelease: false, - }); - const current = semver.coerce(serverVersion, { includePrerelease: false }); - - if (minSupported && current) { - const comparison = semver.compare(minSupported, current); - if (comparison === 0) { - const minPrelease = semver.prerelease(minSupportedVersion)?.at(0); - const currentPrelease = semver.prerelease(serverVersion)?.at(0); - - return ( - typeof minPrelease === 'number' && - typeof currentPrelease === 'number' && - minPrelease <= currentPrelease - ); - } else { - return comparison < 0; - } - } - - return false; -} diff --git a/packages/query-tools/src/metadataPoller.ts b/packages/query-tools/src/metadataPoller.ts index 33a391e6e..b990743cc 100644 --- a/packages/query-tools/src/metadataPoller.ts +++ b/packages/query-tools/src/metadataPoller.ts @@ -1,8 +1,7 @@ import type { CypherVersion } from '@neo4j-cypher/language-support'; import { _internalFeatureFlags, - cypher25Supported, - cypherVersions, + allCypherVersions, DbSchema, Neo4jFunction, Neo4jProcedure, @@ -115,14 +114,13 @@ export class ConnectedMetadataPoller extends MetadataPoller { constructor( databases: Database[], parameters: Record, - serverVersion: string | undefined, + serverCypherVersions: string[] | undefined, private readonly connection: Neo4jConnection, private readonly events: EventEmitter, ) { super(); const supportsCypherAnnotation = - _internalFeatureFlags.cypher25 || - (serverVersion && cypher25Supported(serverVersion)); + _internalFeatureFlags.cypher25 || serverCypherVersions?.includes('25'); this.dbSchema.parameters = parameters; @@ -183,7 +181,7 @@ export class ConnectedMetadataPoller extends MetadataPoller { }); const versions: (CypherVersion | undefined)[] = supportsCypherAnnotation - ? cypherVersions + ? allCypherVersions : [undefined]; versions.forEach((cypherVersion) => { diff --git a/packages/query-tools/src/queries/version.ts b/packages/query-tools/src/queries/version.ts index 0eda27287..79b9dde6c 100644 --- a/packages/query-tools/src/queries/version.ts +++ b/packages/query-tools/src/queries/version.ts @@ -12,12 +12,46 @@ export function getVersion(): ExecuteQueryArgs<{ const resultTransformer = resultTransformers.mappedResultTransformer({ map(record) { const obj = record.toObject(); + const name = obj.name as string; const versions = obj.versions as string[]; - const version = versions?.at(0); - return version; + return { name, versions }; }, - collect(versions, summary) { - return { serverVersion: versions.at(0), summary }; + collect(rows, summary) { + rows.forEach((row) => { + if (row.name === 'Neo4j Kernel') { + return { serverVersion: row.versions, summary }; + } + }); + //We should not reach this unless the "name" field changes + return { serverVersion: undefined, summary }; + }, + }); + + return { + query, + queryConfig: { resultTransformer, routing: 'READ', database: 'system' }, + }; +} + +export function getCypherVersions(): ExecuteQueryArgs<{ + serverCypherVersions: string[] | undefined; +}> { + const query = 'CALL dbms.components() YIELD name, versions'; + + const resultTransformer = resultTransformers.mappedResultTransformer({ + map(record) { + const obj = record.toObject(); + const name = obj.name as string; + const versions = obj.versions as string[]; + return { name, versions }; + }, + collect(rows, summary) { + rows.forEach((row) => { + if (row.name === 'Cypher') { + return { serverCypherVersions: row.versions, summary }; + } + }); + return { serverCypherVersions: ['5'], summary }; }, }); diff --git a/packages/query-tools/src/schemaPoller.ts b/packages/query-tools/src/schemaPoller.ts index 801813066..760450b42 100644 --- a/packages/query-tools/src/schemaPoller.ts +++ b/packages/query-tools/src/schemaPoller.ts @@ -13,7 +13,7 @@ import { } from './metadataPoller'; import { Neo4jConnection } from './neo4jConnection'; import { listDatabases } from './queries/databases.js'; -import { getVersion } from './queries/version'; +import { getCypherVersions } from './queries/version'; export type ConnnectionResult = { success: boolean; @@ -180,18 +180,18 @@ export class Neo4jSchemaPoller { database, ); - const { query: versionQuery, queryConfig: versionQueryConfig } = - getVersion(); - const { serverVersion } = await this.driver.executeQuery( - versionQuery, + const { query: cypherVersionQuery, queryConfig: cypherVersionQueryConfig } = + getCypherVersions(); + const { serverCypherVersions } = await this.driver.executeQuery( + cypherVersionQuery, {}, - versionQueryConfig, + cypherVersionQueryConfig, ); this.metadata = new ConnectedMetadataPoller( databases, this.parameters, - serverVersion, + serverCypherVersions, this.connection, this.events, );