The masking language
A policy for masking columns as rows are read, in S-expressions. quiver types it against every column before anything is read, compiles it and runs it on its render threads.
quiver copy … --mask-script policy.scm --mask-secret "$MASK_SECRET"
quiver copy … --mask 'shop.users.inn=expr:(hash-int value)'
quiver mask --mask-script policy.scm '(phone value)' '+7 (912) 345-67-89'
Values
A column of text, binary, JSON, ENUM, SET or DECIMAL is text; one of integers an integer; DATE a date; DATETIME and TIMESTAMP a time; other types take sql: instead. Every kind has NULL, which goes through functions as in SQL. A value given to a column of another kind, NULL in a NOT NULL column, or a time finer than its column stops the copy.
Forms
| Form | Meaning |
|---|---|
value | The column's value in this row. |
(row COLUMN) | Another column of the row, as read, before any masking. |
"text" 42 true false null | Constants. |
(if CONDITION THEN ELSE) | THEN when CONDITION is true, ELSE when it is false or NULL. |
(cond (CONDITION VALUE) … (else VALUE)) | The first VALUE whose CONDITION is true. |
(let ((NAME VALUE) …) BODY) | Names, each seeing those before it. |
(define (NAME PARAMETER …) BODY) | A function, calling only those defined above it: nothing recurses. |
(mask SELECTOR BODY) | The columns SELECTOR takes get BODY's value; a column takes the first rule naming it, from --mask first, then the scripts in order. |
(keep SELECTOR) | Its columns are copied as they are, and count as decided. |
(permute SELECTOR [LOW HIGH]) | A keyed bijection of its columns, as --mask DB.TABLE.COLUMN=permute: keys stay keys, and every column a foreign key ties to one of them follows unless a rule names it. LOW and HIGH bound the integers. |
(references "DB.TABLE.COLUMN" "DB.TABLE.COLUMN") | The first column refers to the second though no foreign key says so: a permute of either spreads to the other, and masking them differently is an error. |
(strict "DB.TABLE" …) | Every column of these tables must be masked or kept by a rule; otherwise nothing is read, and the message names the columns. |
(words NAME "FILE") | The lines of FILE, beside the script, for pick-word; empty lines and lines starting with # are left out. |
(after-load "DB.TABLE" "STATEMENT") | Run on the target by copy once the rows and keys are in, {table} standing for the table there. |
A SELECTOR is "DB.TABLE.COLUMN" with * and ?, names in backquotes taken as written, or (where (table "DB.TABLE") (column "NAME") (type TYPE …)) with any clause left out; TYPE is one of text, binary, json, integer, decimal, float, date, datetime, timestamp, time, year, enum, set, geometry.
Functions
concatlengthsubstringleftrightupperlowertitletrimreversereplacebeforeafterbefore-lastafter-lastsplit-partindex-ofpad-leftpad-rightrepeatformattranslitonly-digitsredactstrintstarts-withends-withcontainslikeluhn+-*/modabsminmaxround-tofloor-toclampyearmonthdaymake-dateadd-daysdays-betweendate->texttext->datedate-ofseconds-of-daymake-datetimeadd-secondsdatetime->texttext->datetime=<><<=>>=andornotis-nullcoalesceshapeemailhashhash-byteshash-intkeyed-intkeyed-datekeyed-noisekeyed-uuiddigitspickpick-word
Text
Characters are code points: Cyrillic, accents and emoji count one each.
(concat TEXT …) → text
Joins its texts.
| Expression | value | Gives |
|---|---|---|
(concat "user" "-" (str 42)) | NULL | user-42 |
(length TEXT) → integer
How many characters.
| Expression | value | Gives |
|---|---|---|
(length value) | Иван text | 4 |
(substring TEXT START COUNT) → text
COUNT characters from START, counted from 0; what lies outside is left out.
| Expression | value | Gives |
|---|---|---|
(substring value 2 3) | Иван Петров text | ан |
(left TEXT COUNT) → text
The first COUNT characters.
| Expression | value | Gives |
|---|---|---|
(left value 3) | Иван Петров text | Ива |
(right TEXT COUNT) → text
The last COUNT characters.
| Expression | value | Gives |
|---|---|---|
(right value 5) | +7 (912) 345-67-89 text | 67-89 |
(upper TEXT) → text
Latin and Cyrillic letters in upper case.
| Expression | value | Gives |
|---|---|---|
(upper value) | ёлка Tree text | ЁЛКА TREE |
(lower TEXT) → text
Latin and Cyrillic letters in lower case.
| Expression | value | Gives |
|---|---|---|
(lower value) | ЁЛКА Tree text | ёлка tree |
(title TEXT) → text
Each word's first letter in upper case, the others in lower.
| Expression | value | Gives |
|---|---|---|
(title value) | иВАН петров-водкин text | Иван Петров-Водкин |
(trim TEXT) → text
Without the spaces at either end.
| Expression | value | Gives |
|---|---|---|
(trim value) | a b text | a b |
(reverse TEXT) → text
The characters back to front.
| Expression | value | Gives |
|---|---|---|
(reverse value) | абв text | вба |
(replace TEXT FROM TO) → text
Every FROM, left to right, replaced by TO; an empty FROM changes nothing.
| Expression | value | Gives |
|---|---|---|
(replace value "-" "") | 345-67-89 text | 3456789 |
(before TEXT SEPARATOR) → text
What comes before the first SEPARATOR; the whole text when there is none.
| Expression | value | Gives |
|---|---|---|
(before value "@") | anna@mail.ru text | anna |
(after TEXT SEPARATOR) → text
What comes after the first SEPARATOR; empty when there is none.
| Expression | value | Gives |
|---|---|---|
(after value "@") | anna@mail.ru text | mail.ru |
(before-last TEXT SEPARATOR) → text
As before, at the last SEPARATOR.
| Expression | value | Gives |
|---|---|---|
(before-last value ".") | a.b.c text | a.b |
(after-last TEXT SEPARATOR) → text
As after, at the last SEPARATOR.
| Expression | value | Gives |
|---|---|---|
(after-last value ".") | a.b.c text | c |
(split-part TEXT SEPARATOR N) → text
The N-th field between SEPARATORs, from 1; empty past the last.
| Expression | value | Gives |
|---|---|---|
(split-part value "," 2) | a,b,c text | b |
(index-of TEXT PART) → integer
Where PART first starts, in characters from 0; -1 when it is absent, 0 when it is empty.
| Expression | value | Gives |
|---|---|---|
(index-of value "Пе") | Иван Петров text | 5 |
(pad-left TEXT WIDTH FILL) → text
FILL's first character put in front until the text is WIDTH characters long (at most 65535).
| Expression | value | Gives |
|---|---|---|
(pad-left value 5 "0") | 42 text | 00042 |
(pad-right TEXT WIDTH FILL) → text
As pad-left, at the end.
| Expression | value | Gives |
|---|---|---|
(pad-right value 5 ".") | 42 text | 42... |
(repeat TEXT N) → text
N copies, at most 65535; empty past one mebibyte.
| Expression | value | Gives |
|---|---|---|
(repeat "ab" 3) | NULL | ababab |
(format TEMPLATE [TEXT …]) → text
Each {} of TEMPLATE replaced by the next text, in order; the {} left over stay.
| Expression | value | Gives |
|---|---|---|
(format "{}-{}" "a" "b") | NULL | a-b |
(translit TEXT) → text
Cyrillic written in Latin letters, the rest kept.
| Expression | value | Gives |
|---|---|---|
(translit value) | Щука Ёжикова text | Shchuka Yozhikova |
(only-digits TEXT) → text
Only the digits 0 to 9.
| Expression | value | Gives |
|---|---|---|
(only-digits value) | +7 (912) 345 text | 7912345 |
(redact TEXT LEFT RIGHT) → text
Each letter and digit but the first LEFT and the last RIGHT characters becomes *; spaces and punctuation stay.
| Expression | value | Gives |
|---|---|---|
(redact value 2 2) | Иван Петров text | Ив** ****ов |
(str NUMBER) → text
An integer written in decimal.
| Expression | value | Gives |
|---|---|---|
(str (+ value 1)) | 41 integer | 42 |
(int TEXT) → integer
Decimal digits with an optional leading -, spaces around allowed, as an integer; NULL otherwise.
| Expression | value | Gives |
|---|---|---|
(int value) | 42 text | 42 |
(starts-with TEXT PREFIX) → truth
Whether the text starts with PREFIX.
| Expression | value | Gives |
|---|---|---|
(starts-with value "+7") | +7 912 text | true |
(ends-with TEXT SUFFIX) → truth
Whether the text ends with SUFFIX.
| Expression | value | Gives |
|---|---|---|
(ends-with value ".ru") | a@mail.ru text | true |
(contains TEXT PART) → truth
Whether PART is in the text.
| Expression | value | Gives |
|---|---|---|
(contains value "@") | a@b text | true |
(like TEXT PATTERN) → truth
SQL LIKE, with case: % any characters, _ one, a backslash makes the next one plain.
| Expression | value | Gives |
|---|---|---|
(like value "%@%.ru") | a@mail.ru text | true |
(luhn DIGITS) → text
The Luhn check digit of the text's digits, which card numbers end with.
| Expression | value | Gives |
|---|---|---|
(luhn value) | 7992739871 text | 3 |
Numbers
Integers of 64 bits. A function given NULL gives NULL.
(+ A B …) → integer
The sum.
| Expression | value | Gives |
|---|---|---|
(+ value 1) | 41 integer | 42 |
(- A [B]) → integer
A minus B, or minus A alone.
| Expression | value | Gives |
|---|---|---|
(- value 7) | 49 integer | 42 |
(- value) | 42 integer | -42 |
(* A B …) → integer
The product.
| Expression | value | Gives |
|---|---|---|
(* value 2) | 21 integer | 42 |
(/ A B) → integer
The quotient, toward zero; NULL when B is 0.
| Expression | value | Gives |
|---|---|---|
(/ value 2) | 85 integer | 42 |
(/ value 0) | 85 integer | \N |
(mod A B) → integer
The remainder, with A's sign; NULL when B is 0.
| Expression | value | Gives |
|---|---|---|
(mod value 10) | 142 integer | 2 |
(abs A) → integer
Without its sign.
| Expression | value | Gives |
|---|---|---|
(abs value) | -42 integer | 42 |
(min VALUE …) → any
The least of values of one kind.
| Expression | value | Gives |
|---|---|---|
(min value 100) | 42 integer | 42 |
(max VALUE …) → any
The greatest of values of one kind.
| Expression | value | Gives |
|---|---|---|
(max value 0) | -5 integer | 0 |
(round-to N STEP) → integer
To the nearest multiple of STEP, halves away from zero; NULL when STEP is not positive.
| Expression | value | Gives |
|---|---|---|
(round-to value 1000) | 123456 integer | 123000 |
(floor-to N STEP) → integer
Down to a multiple of STEP.
| Expression | value | Gives |
|---|---|---|
(floor-to value 10) | -1 integer | -10 |
(clamp N LOW HIGH) → integer
Kept between LOW and HIGH; NULL when LOW is above HIGH.
| Expression | value | Gives |
|---|---|---|
(clamp value 0 100) | 150 integer | 100 |
Dates and times
A DATE column is a date, DATETIME and TIMESTAMP a date with a time. Years run from 0000 to 9999; what would leave them is NULL. A time finer than its column stops the copy.
(year DATE) → integer
The year.
| Expression | value | Gives |
|---|---|---|
(year value) | 1985-07-14 date | 1985 |
(month DATE) → integer
The month, from 1.
| Expression | value | Gives |
|---|---|---|
(month value) | 1985-07-14 date | 7 |
(day DATE) → integer
The day of the month.
| Expression | value | Gives |
|---|---|---|
(day value) | 1985-07-14 date | 14 |
(make-date YEAR MONTH DAY) → date
The date; NULL for a day that does not exist.
| Expression | value | Gives |
|---|---|---|
(make-date 2024 2 29) | NULL | 2024-02-29 |
(make-date 2023 2 29) | NULL | \N |
(add-days DATE DAYS) → date
So many days later, or earlier when negative.
| Expression | value | Gives |
|---|---|---|
(add-days value 1) | 2000-02-28 date | 2000-02-29 |
(days-between FROM TO) → integer
Days from FROM to TO.
| Expression | value | Gives |
|---|---|---|
(days-between value (make-date 2000 3 1)) | 2000-01-01 date | 60 |
(date->text DATE) → text
YYYY-MM-DD.
| Expression | value | Gives |
|---|---|---|
(date->text value) | 1985-07-14 date | 1985-07-14 |
(text->date TEXT) → date
A date from YYYY-MM-DD; NULL otherwise.
| Expression | value | Gives |
|---|---|---|
(text->date value) | 1985-07-14 text | 1985-07-14 |
(date-of TIME) → date
The day of a time.
| Expression | value | Gives |
|---|---|---|
(date-of value) | 2024-03-01 13:45:10 time | 2024-03-01 |
(seconds-of-day TIME) → integer
Seconds since that day's midnight.
| Expression | value | Gives |
|---|---|---|
(seconds-of-day value) | 2024-03-01 13:45:10 time | 49510 |
(make-datetime DATE SECONDS) → time
That day, SECONDS after midnight, from 0 to 86399.
| Expression | value | Gives |
|---|---|---|
(make-datetime value 3600) | 2024-03-01 date | 2024-03-01 01:00:00 |
(add-seconds TIME SECONDS) → time
So many seconds later.
| Expression | value | Gives |
|---|---|---|
(add-seconds value 90) | 2024-03-01 23:59:00 time | 2024-03-02 00:00:30 |
(datetime->text TIME) → text
YYYY-MM-DD HH:MM:SS, then the fraction of a second if any.
| Expression | value | Gives |
|---|---|---|
(datetime->text value) | 2024-03-01 13:45:10 time | 2024-03-01 13:45:10 |
(text->datetime TEXT) → time
A time from YYYY-MM-DD, HH:MM:SS and up to six fractional digits; NULL otherwise.
| Expression | value | Gives |
|---|---|---|
(text->datetime value) | 2024-03-01 13:45:10.5 text | 2024-03-01 13:45:10.500000 |
Logic
NULL compares to nothing; and, or and if take it as false.
(= A B) → truth
Whether A equals B.
| Expression | value | Gives |
|---|---|---|
(= value "a") | a text | true |
(<> A B) → truth
Whether A differs from B.
| Expression | value | Gives |
|---|---|---|
(<> value 2) | 1 integer | true |
(< A B) → truth
Whether A comes before B: text by bytes, numbers and dates by value.
| Expression | value | Gives |
|---|---|---|
(< value 3) | 3 integer | false |
(<= A B) → truth
Whether A comes before B or equals it.
| Expression | value | Gives |
|---|---|---|
(<= value 3) | 3 integer | true |
(> A B) → truth
Whether A comes after B.
| Expression | value | Gives |
|---|---|---|
(> value "a") | b text | true |
(>= A B) → truth
Whether A comes after B or equals it.
| Expression | value | Gives |
|---|---|---|
(>= value 3) | 2 integer | false |
(and TRUTH …) → truth
Whether all are true.
| Expression | value | Gives |
|---|---|---|
(and (> value 0) (< value 10)) | 5 integer | true |
(or TRUTH …) → truth
Whether one is true.
| Expression | value | Gives |
|---|---|---|
(or (< value 0) (> value 10)) | 50 integer | true |
(not TRUTH) → truth
The opposite.
| Expression | value | Gives |
|---|---|---|
(not (is-null value)) | x text | true |
(is-null VALUE) → truth
Whether the value is NULL.
| Expression | value | Gives |
|---|---|---|
(is-null value) | NULL | true |
(coalesce VALUE …) → any
The first value that is not NULL.
| Expression | value | Gives |
|---|---|---|
(coalesce value "none") | NULL | none |
Keyed
Keyed by --mask-secret with HMAC-SHA256: under one key a seed always gives the same result, in every column and every run, so masked keys still join; without the key the result tells nothing of the seed. The examples are under the key of the secret "example".
(shape TEXT) → text
Each letter and digit replaced by one of its kind (Latin, Cyrillic, digit), any other letter by a Latin one, the rest kept: as many characters, never more bytes.
| Expression | value | Gives |
|---|---|---|
(shape value) | +7 (912) 345-67-89 text | +9 (331) 996-11-65 |
(email TEXT) → text
shape before the last @, the domain kept.
| Expression | value | Gives |
|---|---|---|
(email value) | anna.petrova@mail.ru text | utlr.icmqljt@mail.ru |
(hash TEXT) → text
As many hex digits as the text has characters.
| Expression | value | Gives |
|---|---|---|
(hash value) | Иван text | 39b4 |
(hash-bytes BYTES) → text
As many bytes as the value: for binary columns.
| Expression | value | Gives |
|---|---|---|
(length (hash-bytes value)) | abc text | 3 |
(hash-int NUMBER) → integer
Any 64-bit integer.
| Expression | value | Gives |
|---|---|---|
(hash-int value) | 42 integer | -7709159480299872343 |
(keyed-int SEED LOW HIGH) → integer
An integer from LOW to HIGH.
| Expression | value | Gives |
|---|---|---|
(keyed-int value 18 90) | Иван text | 73 |
(keyed-date SEED FROM TO) → date
A date from FROM to TO.
| Expression | value | Gives |
|---|---|---|
(keyed-date value (make-date 1985 1 1) (make-date 1985 12 31)) | id-1 text | 1985-03-29 |
(keyed-noise N PERCENT) → integer
N moved by up to PERCENT of itself either way, PERCENT from 0 to 100.
| Expression | value | Gives |
|---|---|---|
(keyed-noise value 10) | 100000 integer | 92386 |
(keyed-uuid SEED) → text
A version 4 UUID.
| Expression | value | Gives |
|---|---|---|
(keyed-uuid value) | a text | 33e6f143-b8f1-4bcc-ba78-9920cc303b7f |
(digits SEED COUNT) → text
COUNT decimal digits, at most 65535.
| Expression | value | Gives |
|---|---|---|
(digits value 4) | Иван text | 1463 |
(pick SEED CHOICE …) → any
One of the choices.
| Expression | value | Gives |
|---|---|---|
(pick value "Анна" "Иван" "Олег") | x text | Иван |
(pick-word SEED WORDS) → text
One line of a (words NAME "FILE") list, named here without quotes.