CSV to SQL Importer — JavaScript source
Turn CSV data into SQL import statements: batched multi-row INSERTs, a Postgres COPY FROM STDIN block, or a MySQL LOAD DATA statement. Infers numeric columns, emits NULL for empty fields, sanitizes and de-duplicates header names into SQL identifiers.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// csv-to-sql — pure CSV → SQL import generator. JavaScript port (canonical TS:
// src/lib/csv-to-sql.ts; Go twin: cli/csv-to-sql). RFC 4180 parse, header
// sanitizing into SQL identifiers, then batched INSERTs, a Postgres COPY
// block, or a MySQL LOAD DATA statement. Numeric-looking text emits bare and
// verbatim; empty fields become NULL. Never throws.
'use strict';
function csvToRows(csv) {
const rows = [];
let field = '';
let row = [];
let inQ = false;
for (let i = 0; i < csv.length; i++) {
const ch = csv[i];
if (inQ) {
if (ch === '"') {
if (csv[i + 1] === '"') { field += '"'; i++; } else inQ = false;
} else field += ch;
} else if (ch === '"') inQ = true;
else if (ch === ',') { row.push(field); field = ''; }
else if (ch === '\n') { row.push(field); rows.push(row); row = []; field = ''; }
else if (ch !== '\r') field += ch;
}
if (field.length > 0 || row.length > 0) { row.push(field); rows.push(row); }
return rows;
}
const NUMERIC = /^-?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?$/;
function escapeSqlString(s, dialect) {
let out = s.replace(/'/g, "''");
if (dialect === 'mysql') {
out = out.replace(/\\/g, '\\\\').replace(/\0/g, '\\0').replace(/\n/g, '\\n')
.replace(/\r/g, '\\r').replace(/\x1a/g, '\\Z');
}
return out;
}
function fieldLiteral(value, dialect, inferTypes) {
if (inferTypes) {
if (value === '') return 'NULL';
if (NUMERIC.test(value)) return value; // verbatim — no float round-trip
}
return "'" + escapeSqlString(value, dialect) + "'";
}
function sanitizeIdent(name) {
return name.replace(/[^A-Za-z0-9_]/g, '_');
}
function sanitizeHeaders(headers) {
const seen = new Map();
return headers.map((h, i) => {
let id = sanitizeIdent(h.trim()) || 'col' + (i + 1);
const n = (seen.get(id) || 0) + 1;
seen.set(id, n);
return n > 1 ? id + '_' + n : id;
});
}
function csvEscape(field) {
return /[",\n\r]/.test(field) ? '"' + field.replace(/"/g, '""') + '"' : field;
}
function csvToSql(csv, opts) {
const text = csv.trim();
const rows = csvToRows(text);
if (!text || rows.length < 2) return { ok: false, sql: '', rows: 0, error: 'No rows to import.' };
const { table, format = 'insert', dialect = 'standard', batchSize = 100,
inferTypes = true, quoteIdentifiers = true, fileName = 'import.csv' } = opts;
const cols = sanitizeHeaders(rows[0]);
const data = rows.slice(1);
const identDialect = format === 'copy' ? 'postgres' : format === 'load-data' ? 'mysql' : dialect;
const q = (name) => quoteIdentifiers
? (identDialect === 'mysql' ? '`' + name + '`' : '"' + name + '"') : name;
const tbl = q(sanitizeIdent(table) || 'tbl');
const colList = cols.map(q).join(', ');
if (format === 'copy' || format === 'load-data') {
const payload = [cols.map(csvEscape).join(','),
...data.map((r) => cols.map((_, ci) => csvEscape(r[ci] || '')).join(','))];
if (format === 'copy') {
return { ok: true, rows: data.length, error: null,
sql: `COPY ${tbl} (${colList}) FROM STDIN WITH (FORMAT csv, HEADER true);\n${payload.join('\n')}\n\\.` };
}
const clean = fileName.replace(/[^A-Za-z0-9._\-/]/g, '') || 'import.csv';
return { ok: true, rows: data.length, error: null,
sql: `LOAD DATA LOCAL INFILE '${clean}'\nINTO TABLE ${tbl}\n` +
`FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'\n` +
`LINES TERMINATED BY '\\n'\nIGNORE 1 LINES;\n\n${payload.join('\n')}` };
}
const stmts = [];
for (let i = 0; i < data.length; i += Math.max(1, batchSize)) {
const values = data.slice(i, i + Math.max(1, batchSize)).map((r) =>
' (' + cols.map((_, ci) => fieldLiteral(r[ci] || '', dialect, inferTypes)).join(', ') + ')');
stmts.push(`INSERT INTO ${tbl} (${colList}) VALUES\n${values.join(',\n')};`);
}
return { ok: true, sql: stmts.join('\n'), rows: data.length, error: null };
}
// Example:
// csvToSql('id,name\n1,Ada\n2,', { table: 'users' }).sql
// → 'INSERT INTO "users" ("id", "name") VALUES\n (1, \'Ada\'),\n (2, NULL);'
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →