Skip to content

Instantly share code, notes, and snippets.

@skyjur
Last active January 27, 2020 02:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skyjur/6d776f28202148297c8b851caf990b33 to your computer and use it in GitHub Desktop.
Save skyjur/6d776f28202148297c8b851caf990b33 to your computer and use it in GitHub Desktop.
javascript string literal tag for pg queries
const { query } = require("../connection")
/**
* @template T
* @param {Pg.QueryConfig | string} queryConfig
* @param {Ork.Context} ctx
* @returns {Sql.LazyQuery<T>}
*/
function lazyQuery(queryConfig, ctx = {}) {
let { name = "", text, values = [] } =
typeof queryConfig === "string" ? { text: queryConfig } : queryConfig
name = name || text.replace(/\s+/gm, " ").slice(0, 30) + "..."
return {
get [Symbol.toStringTag]() {
return `LazyQuery<${name}>`
},
name,
text,
values,
run(runCtx = {}) {
return query(this, { ...ctx, ...runCtx })
},
then(onfulfilled, onrejected) {
return this.run().then(onfulfilled, onrejected)
},
catch(onrejected) {
return this.run().catch(onrejected)
}
}
}
module.exports = { lazyQuery }
const safeMarker = Symbol("Safe sql marker")
/**
* @param {TemplateStringsArray} strings
* @args {any[]} args
* @returns {{text: string, values: any[]}}
*/
function sql(strings, ...args) {
let text = strings[0]
let values = []
args.forEach((val, i) => {
if (val && val[safeMarker]) {
const offset = values.length
text += val.text.replace(/\$\d+/g, (match) => {
const j = parseInt(match.slice(1), 10)
return "$" + (offset + j)
})
values.push(...(val.values || []))
text += strings[i + 1]
} else {
values.push(val)
text += "$" + values.length + strings[i + 1]
}
})
return safe({ text, values })
}
sql.id = function id(name) {
if (name instanceof Array && name.length === 2) {
return safe({
text: `${id(name[0]).text}.${id(name[1]).text}`,
values: []
})
} else if (typeof name === "string") {
if (/[^a-zA-Z_]/.test(name)) {
throw new Error(`Invalid pg identifier`)
}
return safe({
text: `"${camelToSnakeKey(name)}"`,
values: []
})
} else {
throw new Error("Invalid type")
}
}
sql.ids = function(names) {
return safe({
text: names.map((name) => this.id(name).text).join(", "),
values: []
})
}
sql.idsIn = function(table, names) {
return sql.ids(names.map((name) => [table, name]))
}
sql.now = sql`now()`
sql.join =
/**
*
* @param {any[]} sqlList
* @returns {Pg.QueryConfig}
*/
function join(sqlList) {
return sqlList.slice(1).reduce((a, b) => sql`${a}, ${b}`, sqlList[0])
}
sql.insert =
/**
* @type {<T extends keyof Sql.Schema>
* (def: Sql.InsertDef<T>) => Pg.QueryConfig}
*/
function insert({ table, data, conflict }) {
let query = sql`
insert into ${sql.id(table)} (
${sql.ids(Object.keys(data))}
)
values (
${sql.join(Object.keys(data).map((key) => data[key]))}
)
`
if (conflict) {
query = sql`${query} on conflict (${sql.ids(conflict.fields)}) do`
const updateFields =
conflict.updateFields === "all"
? Object.keys(data)
: conflict.updateFields
if (updateFields.length > 0) {
query = sql`${query} update set ${sql.join(
updateFields.map(
(key) => sql`${sql.id(key)} = excluded.${sql.id(key)}`
)
)}`
} else {
query = sql`${query} nothing`
}
}
return query
}
/**
* @template T
* @param {T} val
* @returns T
*/
function safe(val) {
val[safeMarker] = true
return val
}
module.exports = { sql, safe }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment