|
2 | 2 |
|
3 | 3 | const debug = require('debug')('gtfs-via-postgres') |
4 | 4 | const sequencify = require('sequencify') |
5 | | -const {inspect} = require('util') |
| 5 | +const {Database} = require('duckdb') |
| 6 | +const {promisify} = require('util') |
6 | 7 | const readCsv = require('gtfs-utils/read-csv') |
7 | | -const {Stringifier} = require('csv-stringify') |
8 | 8 | const formatters = require('./lib') |
9 | 9 | const getDependencies = require('./lib/deps') |
10 | 10 | const pkg = require('./package.json') |
11 | 11 |
|
12 | | -const convertGtfsToSql = async function* (files, opt = {}) { |
| 12 | +const convertGtfsToSql = async (pathToDb, files, opt = {}) => { |
| 13 | + debug('pathToDb', pathToDb) |
| 14 | + |
13 | 15 | opt = { |
14 | 16 | silent: false, |
15 | 17 | requireDependencies: false, |
@@ -41,38 +43,6 @@ const convertGtfsToSql = async function* (files, opt = {}) { |
41 | 43 | debug('deps', deps) |
42 | 44 |
|
43 | 45 | const tasks = { // file name -> [dep name] |
44 | | - 'is_bcp_47_code': { |
45 | | - dep: [], |
46 | | - }, |
47 | | - 'is_timezone': { |
48 | | - dep: [], |
49 | | - }, |
50 | | - ...(tripsWithoutShapeId ? {} : { |
51 | | - 'shape_exists': { |
52 | | - dep: [...deps.shape_exists], |
53 | | - }, |
54 | | - }), |
55 | | - |
56 | | - // special handling of calendar/calendar_dates: |
57 | | - // service_days relies on *both* calendar's & calendar_dates' tables to |
58 | | - // be present, so we add mock tasks here. Each of these mock tasks get |
59 | | - // replaced by a file-based one below if the file has been passed. |
60 | | - 'calendar': { |
61 | | - dep: [], |
62 | | - }, |
63 | | - 'calendar_dates': { |
64 | | - dep: [], |
65 | | - }, |
66 | | - 'service_days': { |
67 | | - dep: ['calendar', 'calendar_dates'], |
68 | | - }, |
69 | | - |
70 | | - // The arrivals_departures & connections views rely on frequencies' table |
71 | | - // to be present, so we add a mock task here. It gets replaced by a |
72 | | - // file-based one below if the file has been passed. |
73 | | - 'frequencies': { |
74 | | - dep: [...deps.frequencies], |
75 | | - }, |
76 | 46 | } |
77 | 47 |
|
78 | 48 | for (const file of files) { |
@@ -100,96 +70,26 @@ const convertGtfsToSql = async function* (files, opt = {}) { |
100 | 70 | sequencify(tasks, Object.keys(tasks), order) |
101 | 71 | debug('order', order) |
102 | 72 |
|
103 | | - yield `\ |
104 | | --- GTFS SQL dump generated by ${pkg.name} v${pkg.version} |
105 | | --- ${pkg.homepage} |
106 | | --- options: |
107 | | -${inspect(opt, {compact: false}).split('\n').map(line => '-- ' + line).join('\n')} |
108 | | -
|
109 | | -\\set ON_ERROR_STOP True |
110 | | -CREATE EXTENSION IF NOT EXISTS postgis; |
111 | | -${opt.schema !== 'public' ? `CREATE SCHEMA IF NOT EXISTS "${opt.schema}";` : ''} |
112 | | -BEGIN; |
113 | | -
|
114 | | -\n` |
| 73 | + const db = new Database(pathToDb) |
| 74 | + const dbRun = promisify(db.run) |
115 | 75 |
|
116 | | - const csv = new Stringifier({quoted: true}) |
| 76 | + await dbRun('BEGIN TRANSACTION') |
117 | 77 |
|
118 | 78 | for (const name of order) { |
119 | 79 | if (!silent) console.error(name) |
120 | 80 | const task = tasks[name] |
121 | | - yield `-- ${name}\n-----------------\n\n` |
122 | 81 |
|
123 | | - const { |
124 | | - beforeAll, |
125 | | - afterAll, |
126 | | - } = formatters[name] |
127 | | - |
128 | | - if ('string' === typeof beforeAll && beforeAll) { |
129 | | - yield beforeAll |
130 | | - } else if ('function' === typeof beforeAll) { |
131 | | - yield beforeAll(opt) |
132 | | - } |
| 82 | + const importData = formatters[name] |
133 | 83 |
|
134 | 84 | if (task.file) { |
135 | | - const {formatRow} = formatters[name] |
136 | | - let nrOfRows = 0 |
137 | | - for await (const rawRow of await readCsv(task.file)) { |
138 | | - const row = formatRow(rawRow, opt) |
139 | | - let formattedRow = null |
140 | | - csv.api.__transform(row, (_formattedRow) => { |
141 | | - formattedRow = _formattedRow |
142 | | - }) |
143 | | - yield formattedRow |
144 | | - nrOfRows++ |
145 | | - } |
146 | | - |
147 | | - if (!silent) console.error(` processed ${nrOfRows} rows`) |
148 | | - } |
149 | | - |
150 | | - if ('string' === typeof afterAll && afterAll) { |
151 | | - yield afterAll + ';\n' |
152 | | - } else if ('function' === typeof afterAll) { |
153 | | - yield afterAll(opt) + ';\n' |
| 85 | + const input = await readCsv(task.file) |
| 86 | + await importData(db, input, opt) |
| 87 | + } else { |
| 88 | + await importData(db, opt) |
154 | 89 | } |
155 | 90 | } |
156 | 91 |
|
157 | | - yield `\ |
158 | | -
|
159 | | -${opt.postgraphile ? `\ |
160 | | --- seal imported data |
161 | | --- todo: |
162 | | --- > Be careful with public schema.It already has a lot of default privileges that you maybe don't want... See documentation[1]. |
163 | | --- > [1]: postgresql.org/docs/11/ddl-schemas.html#DDL-SCHEMAS-PRIV |
164 | | -DO $$ |
165 | | -BEGIN |
166 | | - -- https://stackoverflow.com/questions/8092086/create-postgresql-role-user-if-it-doesnt-exist#8099557 |
167 | | - IF EXISTS ( |
168 | | - SELECT FROM pg_catalog.pg_roles |
169 | | - WHERE rolname = 'postgraphile' |
170 | | - ) THEN |
171 | | - RAISE NOTICE 'Role "postgraphile" already exists, skipping creation.'; |
172 | | - ELSE |
173 | | - CREATE ROLE postgraphile LOGIN PASSWORD 'todo'; -- todo: postgraphile password? |
174 | | - END IF; |
175 | | -END |
176 | | -$$; |
177 | | -DO $$ |
178 | | - DECLARE |
179 | | - db TEXT := current_database(); |
180 | | - BEGIN |
181 | | - EXECUTE format('GRANT ALL PRIVILEGES ON DATABASE %I TO %I', db, 'postgraphile'); |
182 | | - END |
183 | | -$$; |
184 | | -GRANT USAGE ON SCHEMA "${opt.schema}" TO postgraphile; |
185 | | --- https://stackoverflow.com/questions/760210/how-do-you-create-a-read-only-user-in-postgresql#comment50679407_762649 |
186 | | -REVOKE CREATE ON SCHEMA "${opt.schema}" FROM PUBLIC; |
187 | | -GRANT SELECT ON ALL TABLES IN SCHEMA "${opt.schema}" TO postgraphile; |
188 | | --- ALTER DEFAULT PRIVILEGES IN SCHEMA "${opt.schema}" GRANT SELECT ON TABLES TO postgraphile; |
189 | | --- todo: set search_path? https://stackoverflow.com/questions/760210/how-do-you-create-a-read-only-user-in-postgresql#comment33535263_762649 |
190 | | -` : ''} |
191 | | -
|
192 | | -COMMIT;` |
| 92 | + await dbRun('COMMIT') |
193 | 93 | } |
194 | 94 |
|
195 | 95 | module.exports = convertGtfsToSql |
0 commit comments