1+ import type { JoinConfig } from './types' ;
2+
13export async function getTableNames ( db : any ) : Promise < string [ ] > {
24 const result = await db . exec ( "SELECT name FROM sqlite_master WHERE type='table';" ) . get . objs ;
35 return result . map ( ( row : any ) => row . name ) ;
@@ -17,7 +19,93 @@ export async function getRowCount(db: any, tableName: string): Promise<number> {
1719 return result . length > 0 ? result [ 0 ] . count : 0 ;
1820}
1921
20- export async function fetchGeoJSONFeatures ( db : any , table : { name : string ; columns : any [ ] } , layerName : string , layerConfig : any ) {
22+ /**
23+ * Query a table and return all rows as objects
24+ */
25+ export async function queryTable ( db : any , tableName : string , columns ?: string [ ] ) : Promise < Record < string , any > [ ] > {
26+ const columnList = columns ? columns . map ( c => `"${ c } "` ) . join ( ', ' ) : '*' ;
27+ const query = `SELECT ${ columnList } FROM "${ tableName } ";` ;
28+ const result = await db . exec ( query ) . get . objs ;
29+ return result ;
30+ }
31+
32+ /**
33+ * Perform an in-memory join between main data and external data
34+ * @param mainData - Array of records from the main table
35+ * @param joinData - Array of records from the join table
36+ * @param joinConfig - Configuration specifying join keys and type
37+ * @returns Merged array with joined columns added to main records
38+ */
39+ export function performJoin (
40+ mainData : Record < string , any > [ ] ,
41+ joinData : Record < string , any > [ ] ,
42+ joinConfig : JoinConfig
43+ ) : Record < string , any > [ ] {
44+ // Build a lookup map for efficient joining using the right key
45+ const joinLookup = new Map < any , Record < string , any > > ( ) ;
46+ for ( const row of joinData ) {
47+ const key = row [ joinConfig . rightKey ] ;
48+ if ( key !== undefined && key !== null ) {
49+ joinLookup . set ( key , row ) ;
50+ }
51+ }
52+
53+ const joinType = joinConfig . type || 'left' ;
54+ const results : Record < string , any > [ ] = [ ] ;
55+
56+ for ( const mainRow of mainData ) {
57+ const joinKey = mainRow [ joinConfig . leftKey ] ;
58+ const joinRow = joinLookup . get ( joinKey ) ;
59+
60+ if ( joinRow ) {
61+ // Filter columns if specified
62+ let joinedColumns : Record < string , any > ;
63+ if ( joinConfig . columns && joinConfig . columns . length > 0 ) {
64+ joinedColumns = { } ;
65+ for ( const col of joinConfig . columns ) {
66+ if ( col in joinRow ) {
67+ joinedColumns [ col ] = joinRow [ col ] ;
68+ }
69+ }
70+ } else {
71+ // Include all columns from join table (except the join key to avoid duplicates)
72+ joinedColumns = { ...joinRow } ;
73+ // Optionally remove duplicate key if it has the same name
74+ // We keep it for now as it might have different values
75+ }
76+
77+ // Merge: main row properties take precedence, then add joined columns
78+ results . push ( {
79+ ...mainRow ,
80+ ...joinedColumns ,
81+ } ) ;
82+ } else if ( joinType === 'left' ) {
83+ // No match found, but left join keeps the main row
84+ results . push ( { ...mainRow } ) ;
85+ }
86+ // For 'inner' join, skip rows with no match
87+ }
88+
89+ return results ;
90+ }
91+
92+ /**
93+ * Fetch GeoJSON features from a table, optionally merging joined data
94+ * @param db - The main database connection
95+ * @param table - Table metadata with name and columns
96+ * @param layerName - Name of the layer for feature properties
97+ * @param layerConfig - Layer configuration
98+ * @param joinedData - Optional pre-joined data to merge into features (keyed by join column)
99+ * @param joinConfig - Optional join configuration specifying the key column
100+ */
101+ export async function fetchGeoJSONFeatures (
102+ db : any ,
103+ table : { name : string ; columns : any [ ] } ,
104+ layerName : string ,
105+ layerConfig : any ,
106+ joinedData ?: Map < any , Record < string , any > > ,
107+ joinConfig ?: JoinConfig
108+ ) {
21109 const columnNames = table . columns
22110 . filter ( ( c : any ) => c . name . toLowerCase ( ) !== 'geometry' )
23111 . map ( ( c : any ) => `"${ c . name } "` )
@@ -33,15 +121,46 @@ export async function fetchGeoJSONFeatures(db: any, table: { name: string; colum
33121 ` ;
34122 const rows = await db . exec ( query ) . get . objs ;
35123 const features : any [ ] = [ ] ;
124+
125+ const joinType = joinConfig ?. type || 'left' ;
126+
36127 for ( const row of rows ) {
37128 if ( ! row . geojson_geom ) continue ;
129+
130+ // Build base properties from main table
38131 const properties : any = { _table : table . name , _layer : layerName , _layerConfig : layerConfig } ;
39132 for ( const col of table . columns ) {
40133 const key = col . name ;
41134 if ( key . toLowerCase ( ) !== 'geometry' && key !== 'geojson_geom' && key !== 'geom_type' ) {
42135 properties [ key ] = row [ key ] ;
43136 }
44137 }
138+
139+ // Merge joined data if available
140+ if ( joinedData && joinConfig ) {
141+ const joinKey = row [ joinConfig . leftKey ] ;
142+ const joinRow = joinedData . get ( joinKey ) ;
143+
144+ if ( joinRow ) {
145+ // Add joined columns to properties
146+ for ( const [ key , value ] of Object . entries ( joinRow ) ) {
147+ // Don't overwrite existing properties (main table takes precedence)
148+ if ( ! ( key in properties ) ) {
149+ properties [ key ] = value ;
150+ } else if ( key !== joinConfig . rightKey ) {
151+ // If column name conflicts, prefix with table name
152+ properties [ `${ joinConfig . table } _${ key } ` ] = value ;
153+ }
154+ }
155+ properties . _hasJoinedData = true ;
156+ } else if ( joinType === 'inner' ) {
157+ // Skip this feature for inner join when no match
158+ continue ;
159+ } else {
160+ properties . _hasJoinedData = false ;
161+ }
162+ }
163+
45164 features . push ( { type : 'Feature' , geometry : row . geojson_geom , properties } ) ;
46165 }
47166 return features ;
0 commit comments