11import type { CypherVersion } from '@neo4j-cypher/language-support' ;
22import { resultTransformers } from 'neo4j-driver' ;
3- import { ExecuteQueryArgs } from '../types/sdkTypes' ;
3+ import { ExecuteQueryArgs , DbType } from '../types/sdkTypes' ;
44
55export type DatabaseStatus =
66 | 'online'
@@ -26,14 +26,38 @@ export type Database = {
2626 default : boolean ;
2727 home : boolean ;
2828 aliases ?: string [ ] ; // introduced in neo4j 4.4
29- type ?: 'system' | 'composite' | 'standard' ; // introduced in neo4j 5
29+ type ?: 'system' | 'composite' | 'standard' | 'graph shard' | 'property shard' ; // introduced in neo4j 5
3030 // "new keys", not sure which version introduced
3131 writer ?: boolean ;
3232 access ?: string ;
3333 constituents ?: string [ ] ;
3434 defaultLanguage ?: CypherVersion ;
3535} ;
3636
37+ const findComposite = ( databases : Database [ ] , alias : string ) => {
38+ const compositeDatabases = databases . filter ( ( db ) => db . type === 'composite' ) ;
39+ return compositeDatabases . find ( ( db ) => db . constituents ?. includes ( alias ) ) ;
40+ } ;
41+
42+ const isCompositeAlias = ( databases : Database [ ] , alias : string ) =>
43+ findComposite ( databases , alias ) !== undefined ;
44+
45+ const removeCompositeAliases = ( databases : Database [ ] ) : Database [ ] => {
46+ return databases . map ( ( { aliases, ...db } ) => ( {
47+ ...db ,
48+ aliases : aliases ?. filter ( ( alias ) => ! isCompositeAlias ( databases , alias ) ) ,
49+ } ) ) ;
50+ } ;
51+
52+ const removeSPDShards = ( databases : Database [ ] ) : Database [ ] => {
53+ return databases . filter ( ( db ) => {
54+ return ! (
55+ db . type !== undefined &&
56+ ( db . type === DbType . GRAPH_SHARD || db . type === DbType . PROPERTY_SHARD )
57+ ) ;
58+ } ) ;
59+ } ;
60+
3761/**
3862 * List available databases in your dbms
3963 * https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-show-databases
@@ -43,7 +67,7 @@ export function listDatabases(): ExecuteQueryArgs<{
4367} > {
4468 const query = 'SHOW DATABASES YIELD *' ;
4569
46- const resultTransformer = resultTransformers . mappedResultTransformer ( {
70+ const resultTransformer = resultTransformers . mapped ( {
4771 map ( record ) {
4872 const obj = record . toObject ( ) ;
4973 if ( obj . defaultLanguage ) {
@@ -52,8 +76,21 @@ export function listDatabases(): ExecuteQueryArgs<{
5276 return obj as Database ;
5377 } ,
5478 collect ( databases , summary ) {
79+ const instancesRecord : Record < string , Database > = databases . reduce <
80+ Record < string , Database >
81+ > ( ( acc , db ) => {
82+ const instanceId : string = `${ db . name } @${ db . address } ` ;
83+ acc [ instanceId ] = db ;
84+ return acc ;
85+ } , { } ) ;
86+ const logicalDatabases : Record < string , Database > =
87+ getLogicalDatabases ( instancesRecord ) ;
88+ const uiDatabases = removeSPDShards (
89+ removeCompositeAliases ( Object . values ( logicalDatabases ) ) ,
90+ ) ;
91+
5592 return {
56- databases : databases . filter ( ( x ) => x ?. writer === true ) ,
93+ databases : sortDatabases ( uiDatabases ) ,
5794 summary,
5895 } ;
5996 } ,
@@ -64,3 +101,69 @@ export function listDatabases(): ExecuteQueryArgs<{
64101 queryConfig : { resultTransformer, routing : 'READ' , database : 'system' } ,
65102 } ;
66103}
104+
105+ /**
106+ * In clustered environments a single logical database can be backed by multiple physical database instances
107+ * running on a different servers.
108+ * This function returns a record of logical databases.
109+ * A database instance is uniquely identified by its name and address.
110+ * A logical database is uniquely identified by its name.
111+ * @input instances - a record of database instances
112+ * @output a record of logical databases
113+ */
114+ export function getLogicalDatabases (
115+ instances : Record < string , Database > ,
116+ ) : Record < string , Database > {
117+ // Two databases with the same name but different properties will be merged into one
118+ // merging rules:
119+ // - if writer (leader) is present, use it, otherwise use the first one
120+
121+ // Merge db2 into db1
122+ const mergeDatabases = ( db1 : Database , db2 : Database ) : Database => {
123+ let mergedDatabase = { ...db1 } ;
124+ if ( db2 . role === 'leader' || db2 . writer === true ) {
125+ mergedDatabase = { ...db2 } ;
126+ }
127+
128+ return mergedDatabase ;
129+ } ;
130+
131+ return Object . values ( instances ) . reduce < Record < string , Database > > (
132+ ( databases , instance ) => {
133+ const key = instance . name ;
134+ const existingDatabase = databases [ key ] ;
135+ const mergedDatabase = existingDatabase
136+ ? mergeDatabases ( existingDatabase , instance )
137+ : instance ;
138+
139+ return {
140+ ...databases ,
141+ [ key ] : mergedDatabase ,
142+ } ;
143+ } ,
144+ { } ,
145+ ) ;
146+ }
147+
148+ export function sortDatabases ( databases : Database [ ] ) {
149+ function databaseComparator ( a : Database , b : Database ) {
150+ // disable eslint to make code more readable
151+ /* eslint-disable curly */
152+ // home is greater than default
153+ if ( a . default && b . home ) return 1 ;
154+
155+ // otherwiser default is greater than anything else
156+ if ( a . default ) return - 1 ;
157+ if ( b . default ) return 1 ;
158+
159+ // system is less than anything else
160+ if ( a . name === 'system' ) return 1 ;
161+ if ( b . name === 'system' ) return - 1 ;
162+ /* eslint-enable curly */
163+
164+ // else sort alphabetically
165+ return a . name . localeCompare ( b . name ) ;
166+ }
167+
168+ return databases . sort ( databaseComparator ) ;
169+ }
0 commit comments