forked from OHDSI/WebAPI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsources.js
More file actions
172 lines (153 loc) · 6.38 KB
/
Copy pathsources.js
File metadata and controls
172 lines (153 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import sql from 'mssql'
import config from './config.js'
import { loadSql, renderSql } from './sqlrender.js'
// One mssql connection pool per source, keyed by sourceKey
const pools = new Map()
function buildPoolConfig (source) {
return {
server: source.server,
port: source.port || 1433,
database: source.database,
user: source.username,
password: source.password,
connectionString: source.connectionString || undefined,
options: {
encrypt: source.encrypt !== false,
trustServerCertificate: source.trustServerCertificate || false,
enableArithAbort: true
},
requestTimeout: 60000,
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30000
}
}
}
// Create achilles_result_concept_count if missing and populate it from achilles_results.
// Runs in the background after pool connects — does not block startup.
async function initConceptCount (pool, source) {
if (!source.resultsSchema || !source.vocabSchema) return
const createSql = renderSql(loadSql('ddl/concept_count_create.sql'), source)
await pool.request().query(createSql)
const countSql = renderSql('SELECT COUNT(*) AS cnt FROM @results_database_schema.achilles_result_concept_count', source)
try {
const { recordset } = await pool.request().query(countSql)
if (recordset[0].cnt > 0) return
} catch (err) {
if (err.number === 208) return // achilles_results doesn't exist yet
throw err
}
const ddlCfg = source.connectionString
? { connectionString: source.connectionString, requestTimeout: 0 }
: { ...buildPoolConfig(source), requestTimeout: 0 }
const ddlPool = await new sql.ConnectionPool(ddlCfg).connect()
try {
console.log(`[${source.sourceKey}] Populating achilles_result_concept_count...`)
const populateSql = renderSql(loadSql('ddl/concept_count_populate.sql'), source)
await ddlPool.request().query(populateSql)
console.log(`[${source.sourceKey}] achilles_result_concept_count populated`)
} finally {
await ddlPool.close()
}
}
// Create concept_hierarchy if missing and populate it if empty.
// Runs in the background after pool connects — does not block startup.
async function initConceptHierarchy (pool, source) {
if (!source.resultsSchema || !source.vocabSchema) return
const createSql = renderSql(loadSql('ddl/concept_hierarchy_create.sql'), source)
await pool.request().query(createSql)
const countSql = renderSql('SELECT COUNT(*) AS cnt FROM @results_database_schema.concept_hierarchy', source)
const { recordset } = await pool.request().query(countSql)
if (recordset[0].cnt > 0) return
// requestTimeout is baked into the tedious connection, not settable per-request,
// so use a dedicated pool with no timeout for the long-running vocab joins.
const ddlCfg = source.connectionString
? { connectionString: source.connectionString, requestTimeout: 0 }
: { ...buildPoolConfig(source), requestTimeout: 0 }
const ddlPool = await new sql.ConnectionPool(ddlCfg).connect()
try {
console.log(`[${source.sourceKey}] Populating concept_hierarchy (this may take several minutes)...`)
const populateSql = renderSql(loadSql('ddl/concept_hierarchy_populate.sql'), source)
await ddlPool.request().query(populateSql)
console.log(`[${source.sourceKey}] concept_hierarchy populated`)
} finally {
await ddlPool.close()
}
}
async function initCohortTable (pool, source) {
if (!source.resultsSchema) return
const createSql = renderSql(loadSql('ddl/cohort_table_create.sql'), source)
await pool.request().query(createSql)
}
async function initStatsTable (pool, source) {
if (!source.resultsSchema) return
const createSql = renderSql(loadSql('ddl/cohort_stats_create.sql'), source)
await pool.request().query(createSql)
}
// Eagerly open all pools on startup
export async function initSources () {
for (const source of config.sources) {
try {
const pool = source.connectionString
? await new sql.ConnectionPool(source.connectionString).connect()
: await new sql.ConnectionPool(buildPoolConfig(source)).connect()
pools.set(source.sourceKey, pool)
console.log(`Connected to source: ${source.sourceKey}`)
initCohortTable(pool, source).catch(err =>
console.error(`[${source.sourceKey}] cohort table init failed: ${err.message}`)
)
initStatsTable(pool, source).catch(err =>
console.error(`[${source.sourceKey}] cohort stats tables init failed: ${err.message}`)
)
initConceptHierarchy(pool, source).catch(err =>
console.error(`[${source.sourceKey}] concept_hierarchy init failed: ${err.message}`)
)
initConceptCount(pool, source).catch(err =>
console.error(`[${source.sourceKey}] achilles_result_concept_count init failed: ${err.message}`)
)
} catch (err) {
console.error(`Failed to connect to source ${source.sourceKey}: ${err.message}`)
}
}
}
// Get a connected pool by sourceKey; throws if not found
export function getPool (sourceKey) {
const pool = pools.get(sourceKey)
if (!pool) throw Object.assign(new Error(`Unknown source: ${sourceKey}`), { status: 404 })
return pool
}
// Get source config object by sourceKey; throws if not found
export function getSource (sourceKey) {
const source = config.sources.find(s => s.sourceKey === sourceKey)
if (!source) throw Object.assign(new Error(`Unknown source: ${sourceKey}`), { status: 404 })
return source
}
// Build the Atlas-compatible SourceInfo shape for one source
export function toSourceInfo (source, index) {
const id = index + 1
const daimonBase = id * 10
const daimons = []
if (source.cdmSchema) {
daimons.push({ sourceDaimonId: daimonBase + 1, daimonType: 'CDM', tableQualifier: source.cdmSchema, priority: 0 })
}
if (source.vocabSchema) {
daimons.push({ sourceDaimonId: daimonBase + 2, daimonType: 'Vocabulary', tableQualifier: source.vocabSchema, priority: 0 })
}
if (source.resultsSchema) {
daimons.push({ sourceDaimonId: daimonBase + 3, daimonType: 'Results', tableQualifier: source.resultsSchema, priority: 0 })
}
if (source.tempSchema) {
daimons.push({ sourceDaimonId: daimonBase + 4, daimonType: 'Temp', tableQualifier: source.tempSchema, priority: 0 })
}
return {
sourceId: id,
sourceName: source.sourceName,
sourceKey: source.sourceKey,
sourceDialect: 'sql server',
daimons
}
}
export function getAllSourceInfos () {
return config.sources.map((s, i) => toSourceInfo(s, i))
}