lobos1.0.0-SNAPSHOTA library to create and manipulate SQL database schemas. dependencies
dev dependencies
| (this space intentionally left almost blank) | ||||||||||||||||||||||||
This namespace include the | (ns lobos.compiler (:refer-clojure :exclude [compile defonce replace]) (:require (lobos [ast :as ast])) (:use (clojure [string :only [replace]]) lobos.utils) (:import (java.lang UnsupportedOperationException))) | ||||||||||||||||||||||||
(ast/import-all) | |||||||||||||||||||||||||
Compiler | |||||||||||||||||||||||||
When no implementation specific method is found, fall back on the default standard compiler. | (def backends-hierarchy (atom (-> (make-hierarchy) (derive :h2 ::standard) (derive :mysql ::standard) (derive :postgresql ::standard) (derive :sqlite ::standard) (derive :sqlserver ::standard)))) | ||||||||||||||||||||||||
Compiles the given object into SQL. | (defmulti compile (fn [o] [(-> o :db-spec :subprotocol (or ::standard) keyword) (type o)]) :hierarchy backends-hierarchy) | ||||||||||||||||||||||||
Helpers | |||||||||||||||||||||||||
Constructs an IdentifierExpression AST form and compiles it. Takes an
optional | (defn as-identifier [db-spec name & qualifiers] (compile (IdentifierExpression. db-spec name qualifiers))) | ||||||||||||||||||||||||
Throws an UnsupportedOperationException using the given message. Can be given a condition, in which case it throw an exception only if it's true. | (defn unsupported ([msg] (unsupported true msg)) ([cond msg] (when cond (throw (UnsupportedOperationException. (str msg)))))) | ||||||||||||||||||||||||
Constructs a Mode AST form with the specified | (defn mode [db-spec] (Mode. db-spec)) | ||||||||||||||||||||||||
Used by the | (defn- extract-foreign-keys* [table] (let [fkey? #(instance? ForeignKeyConstraintDefinition %) foreign-keys (vector (:tname table) (->> (:elements table) (filter fkey?))) table (update-in table [:elements] (fn [es] (filter #(not (fkey? %)) es)))] [table foreign-keys])) | ||||||||||||||||||||||||
Given a collection of AST statements, extract all foreign key constraints for all tables and returns a vector composed of the statements (without the foreign keys) and a map of the foreign keys grouped by table name. | (defn extract-foreign-keys [elements] (let [tables (filter #(instance? CreateTableStatement %) elements) others (filter #(not (instance? CreateTableStatement %)) elements) results (map extract-foreign-keys* tables) tables (map first results) foreign-keys (apply hash-map (apply concat (map second results)))] [(concat tables others) foreign-keys])) | ||||||||||||||||||||||||
Helper used when compiling a create schema statement that require the foreign keys to be extracted and added individually using alter add statements. | (defn build-alter-add-statements [db-spec m] (for [[tname elements] m element elements] (AlterTableStatement. db-spec tname :add element))) | ||||||||||||||||||||||||
Default compiler | |||||||||||||||||||||||||
The default compiler is based on the SQL standard and does not prefer any particular implementation. | |||||||||||||||||||||||||
Compiling | (defmethod compile [::standard Mode] [_] nil) | ||||||||||||||||||||||||
Expressions | |||||||||||||||||||||||||
Keywords will be made into SQL keywords using the | (defmethod compile [::standard ScalarExpression] [expression] (let [{:keys [scalar]} expression] (cond (keyword? scalar) (str (as-sql-keyword scalar)) (string? scalar) (str "'" scalar "'") :else scalar))) | ||||||||||||||||||||||||
(defmethod compile [::standard IdentifierExpression] [identifier] (let [{:keys [db-spec name qualifiers]} identifier] (join* \. (->> (concat qualifiers [name]) (filter identity) (map #(when % (as-str \" % \"))))))) | |||||||||||||||||||||||||
(defmethod compile [::standard FunctionExpression] [function] (let [{:keys [db-spec name args]} function] (str (as-sql-keyword name) (as-list (map compile args))))) | |||||||||||||||||||||||||
(defmethod compile [::standard OperatorExpression] [operator] (let [{:keys [db-spec op left right]} operator] (if (vector? (first right)) (str "(" (compile left) " " (as-sql-keyword op) " " (as-list (map compile (first right))) ")") (str "(" (apply join (str " " (as-sql-keyword op) " ") (map compile (conj right left))) ")")))) | |||||||||||||||||||||||||
| (defmethod compile [::standard DataTypeClause] [expression] (let [{:keys [dtype args options]} expression {:keys [encoding collate time-zone]} options] (join \space (str (as-sql-keyword dtype) (as-list args)) (when encoding ["CHARACTER SET" (as-str encoding)]) (when collate ["COLLATE" (as-str collate)]) (when time-zone ["WITH TIME ZONE"])))) | ||||||||||||||||||||||||
Clauses | |||||||||||||||||||||||||
Standard | (defmethod compile [::standard AutoIncClause] [_] "GENERATED ALWAYS AS IDENTITY") | ||||||||||||||||||||||||
Definitions | |||||||||||||||||||||||||
| (defmethod compile [::standard ColumnDefinition] [definition] (let [{:keys [db-spec cname data-type default auto-inc not-null others]} definition] (apply join \space (as-identifier db-spec cname) (compile data-type) (when default (str "DEFAULT " (compile default))) (when auto-inc (compile auto-inc)) (when not-null "NOT NULL") others))) | ||||||||||||||||||||||||
| (defmethod compile [::standard UniqueConstraintDefinition] [definition] (let [{:keys [db-spec cname ctype columns]} definition] (join \space "CONSTRAINT" (as-identifier db-spec cname) (as-sql-keyword ctype) (as-list (map (partial as-identifier db-spec) columns))))) | ||||||||||||||||||||||||
| (defmethod compile [::standard ForeignKeyConstraintDefinition] [definition] (let [{:keys [db-spec cname columns parent-table parent-columns match triggered-actions]} definition] (join \space "CONSTRAINT" (as-identifier db-spec cname) "FOREIGN KEY" (as-list (map (partial as-identifier db-spec) columns)) "REFERENCES" (as-identifier db-spec parent-table (:schema db-spec)) (as-list (map (partial as-identifier db-spec) parent-columns)) (when match (str "MATCH " (as-sql-keyword match))) (when (contains? triggered-actions :on-delete) (str "ON DELETE " (as-sql-keyword (:on-delete triggered-actions)))) (when (contains? triggered-actions :on-update) (str "ON UPDATE " (as-sql-keyword (:on-update triggered-actions))))))) | ||||||||||||||||||||||||
(defmethod compile [::standard CheckConstraintDefinition] [definition] (let [{:keys [db-spec cname condition]} definition] (join \space "CONSTRAINT" (as-identifier db-spec cname) "CHECK" (compile condition)))) | |||||||||||||||||||||||||
Create and Drop Statements | |||||||||||||||||||||||||
The default create statement will extract all foreign key constraint from its create table statements to be added afterward using alter add statements. | (defmethod compile [::standard CreateSchemaStatement] [statement] (let [{:keys [db-spec sname elements]} statement [elements foreign-keys] (extract-foreign-keys elements) alters (map compile (build-alter-add-statements (assoc db-spec :schema sname) foreign-keys))] (conj alters (str "CREATE SCHEMA " (apply join "\n" (conj (map compile elements) (as-identifier db-spec sname))))))) | ||||||||||||||||||||||||
| (defmethod compile [::standard CreateTableStatement] [statement] (let [{:keys [db-spec tname elements]} statement] (format "CREATE TABLE %s %s" (as-identifier db-spec tname (:schema db-spec)) (or (as-list (map compile elements)) "()")))) | ||||||||||||||||||||||||
(defmethod compile [::standard CreateIndexStatement] [statement] (let [{:keys [db-spec iname tname columns options]} statement index-column #(if (keyword? %) (as-identifier db-spec %) (let [col (first %) options (set (rest %))] (apply join \space (as-identifier db-spec (first %)) (map as-sql-keyword options))))] (format "CREATE %sINDEX %s ON %s %s" (str (when ((set options) :unique) "UNIQUE ")) (as-identifier db-spec iname) (as-identifier db-spec tname (:schema db-spec)) (as-list (map index-column columns))))) | |||||||||||||||||||||||||
(defmethod compile [::standard DropStatement] [statement] (let [{:keys [db-spec otype oname behavior]} statement] (join \space "DROP" (as-sql-keyword otype) (as-identifier db-spec oname (:schema db-spec)) (as-sql-keyword behavior)))) | |||||||||||||||||||||||||
Alter Statement and Actions | |||||||||||||||||||||||||
(defmethod compile [::standard AlterAddAction] [action] (let [{:keys [db-spec element]} action element (assoc element :db-spec db-spec)] (join \space "ADD" (compile element)))) | |||||||||||||||||||||||||
(defmethod compile [::standard AlterDropAction] [action] (let [{:keys [db-spec element]} action is-column (instance? ColumnDefinition element)] (join \space "DROP" (if is-column "COLUMN" "CONSTRAINT") (as-identifier db-spec (:cname element))))) | |||||||||||||||||||||||||
(defmethod compile [::standard AlterModifyAction] [action] (let [{:keys [db-spec element]} action default (:default element)] (if default (join \space "ALTER COLUMN" (as-identifier db-spec (:cname element)) (if (= default :drop) "DROP DEFAULT" (str "SET DEFAULT " (compile default)))) (unsupported "Only set/drop default supported.")))) | |||||||||||||||||||||||||
| (defmethod compile [::standard AlterRenameAction] [action] (let [{:keys [db-spec element]} action] (unsupported "Rename action not supported."))) | ||||||||||||||||||||||||
| (defmethod compile [::standard AlterTableStatement] [statement] (let [{:keys [db-spec tname action element]} statement element (assoc element :sname (:schema db-spec) :tname tname)] (join \space "ALTER TABLE" (as-identifier db-spec tname (:schema db-spec)) (case action :add (compile (AlterAddAction. db-spec element)) :drop (compile (AlterDropAction. db-spec element)) :modify (compile (AlterModifyAction. db-spec element)) :rename (compile (AlterRenameAction. db-spec element)))))) | ||||||||||||||||||||||||
Compiler implementation for H2. | (ns lobos.backends.h2 (:refer-clojure :exclude [compile defonce]) (:require (lobos [schema :as schema])) (:use (clojure [string :only [split]]) (lobos analyzer compiler connectivity internal metadata utils)) (:import (lobos.ast AlterRenameAction AutoIncClause CreateSchemaStatement DataTypeClause DropStatement) (lobos.schema ForeignKeyConstraint UniqueConstraint))) | ||||||||||||||||||||||||
Analyzer | |||||||||||||||||||||||||
(defmethod analyze [:h2 UniqueConstraint] [_ sname tname cname meta] (let [columns (split (:column_list meta) #",") ctype (-> meta :constraint_type as-keyword)] (UniqueConstraint. (make-index-name tname ctype columns) ctype (map as-keyword columns)))) | |||||||||||||||||||||||||
(defmethod analyze [:h2 :constraints] [_ sname tname] (let [db-spec (db-meta-spec)] (concat (map (fn [meta] (analyze UniqueConstraint sname tname (-> meta :constraint_name keyword) meta)) (query db-spec :INFORMATION_SCHEMA :CONSTRAINTS (and (or (= :CONSTRAINT_TYPE "UNIQUE") (= :CONSTRAINT_TYPE "PRIMARY KEY")) (= :TABLE_SCHEMA (as-str sname)) (= :TABLE_NAME (as-str tname))))) (map (fn [[cname meta]] (analyze ForeignKeyConstraint cname meta)) (references-meta sname tname))))) | |||||||||||||||||||||||||
Compiler | |||||||||||||||||||||||||
(defmethod compile [:h2 DataTypeClause] [expression] (let [{:keys [dtype args options]} expression] (unsupported (= dtype :binary) "Use varbinary instead.") (unsupported (:time-zone options) "Time zones not supported.") (str (as-sql-keyword dtype) (as-list args)))) | |||||||||||||||||||||||||
(defmethod compile [:h2 AutoIncClause] [_] "AUTO_INCREMENT") | |||||||||||||||||||||||||
(defmethod compile [::standard CreateSchemaStatement] [statement] (let [{:keys [db-spec sname elements]} statement [elements foreign-keys] (extract-foreign-keys elements) alters (map compile (build-alter-add-statements (assoc db-spec :schema sname) foreign-keys))] (conj alters (str "CREATE SCHEMA " (apply join "\n" (conj (map compile elements) (as-identifier db-spec sname))))))) | |||||||||||||||||||||||||
(defmethod compile [:h2 CreateSchemaStatement] [statement] (let [{:keys [db-spec sname elements]} statement [elements foreign-keys] (extract-foreign-keys elements) alters (map compile (build-alter-add-statements (assoc db-spec :schema sname) foreign-keys))] (conj (concat (map (comp compile #(assoc-in % [:db-spec :schema] sname)) elements) alters) (str "CREATE SCHEMA " (as-identifier db-spec sname))))) | |||||||||||||||||||||||||
(defmethod compile [:h2 DropStatement] [statement] (let [{:keys [db-spec otype oname behavior]} statement] (join \space "DROP" (as-sql-keyword otype) (as-identifier db-spec oname (:schema db-spec)) (when (and behavior (#{:table} otype)) (as-sql-keyword behavior))))) | |||||||||||||||||||||||||
(defmethod compile [:h2 AlterRenameAction] [action] (let [{:keys [db-spec element]} action old-name (:cname element) new-name (:others element)] (format "ALTER COLUMN %s RENAME TO %s" (as-identifier db-spec old-name) (as-identifier db-spec new-name)))) | |||||||||||||||||||||||||
Compiler implementation for MySQL. | (ns lobos.backends.mysql (:refer-clojure :exclude [compile defonce]) (:require (lobos [schema :as schema] [ast :as ast])) (:use (lobos [schema :only [build-definition]] analyzer compiler metadata utils)) (:import (lobos.schema Column DataType Schema UniqueConstraint))) | ||||||||||||||||||||||||
(ast/import-all) | |||||||||||||||||||||||||
Analyzer | |||||||||||||||||||||||||
(def ^{:private true} analyzer-data-type-aliases {:bit :boolean :int :integer :text :clob :tinyblob :blob :tinytext :clob}) | |||||||||||||||||||||||||
(defmethod analyze [:mysql DataType] [_ column-meta] (let [dtype (-> column-meta :type_name as-keyword) dtype (first (replace analyzer-data-type-aliases [dtype]))] (schema/data-type dtype (if (#{:time :timestamp} dtype) [] (analyze-data-type-args dtype column-meta))))) | |||||||||||||||||||||||||
(defmethod analyze [:mysql UniqueConstraint] [_ sname tname cname index-meta] (let [pkeys (primary-keys sname tname) pkey (pkeys (keyword cname)) columns (vec (map #(-> % :column_name keyword) index-meta))] (UniqueConstraint. (if pkey (make-index-name tname :primary-key columns) (keyword cname)) (if pkey :primary-key :unique) columns))) | |||||||||||||||||||||||||
(defn- analyze-column [sname tname cname] (analyze Column (first (resultset-seq (.getColumns (db-meta) (name sname) nil (name tname) (name cname)))))) | |||||||||||||||||||||||||
(defmethod analyze [:mysql Schema] [_ sname] (let [db-spec (db-meta-spec) sname (or sname (->> db-spec :subname (re-find #"//.*/(.+)$") second))] (when-not sname (throw (java.sql.SQLException. "No database selected"))) (analyze [:lobos.analyzer/standard Schema] sname))) | |||||||||||||||||||||||||
Compiler | |||||||||||||||||||||||||
(defmethod compile [:mysql IdentifierExpression] [identifier] (let [{:keys [db-spec name qualifiers]} identifier] (join* \. (->> (concat qualifiers [name]) (filter identity) (map #(when % (as-str \` % \`))))))) | |||||||||||||||||||||||||
(def ^{:private true} compiler-data-type-aliases {:clob :text :nclob :text}) | |||||||||||||||||||||||||
(defmethod compile [:mysql DataTypeClause] [expression] (let [{:keys [dtype args options]} expression {:keys [encoding collate]} options encoding (when (= dtype :nclob) "UTF8") dtype (first (replace compiler-data-type-aliases [dtype])) args (if (#{:time :timestamp} dtype) [] args)] (unsupported (= dtype :real) "Use double instead.") (unsupported (:time-zone options) "Time zones not supported.") (join \space (str (as-sql-keyword dtype) (as-list args)) (when encoding (str "CHARACTER SET " (as-str encoding))) (when collate (str "COLLATE " (as-str collate)))))) | |||||||||||||||||||||||||
(defmethod compile [:mysql AutoIncClause] [_] "AUTO_INCREMENT") | |||||||||||||||||||||||||
(defmethod compile [:mysql CreateSchemaStatement] [statement] (let [{:keys [db-spec sname elements]} statement [elements foreign-keys] (extract-foreign-keys elements) alters (map compile (build-alter-add-statements (assoc db-spec :schema sname) foreign-keys))] (conj (concat (map (comp compile #(assoc-in % [:db-spec :schema] sname)) elements) alters) (str "CREATE SCHEMA " (as-identifier db-spec sname))))) | |||||||||||||||||||||||||
(defmethod compile [:mysql DropStatement] [statement] (let [{:keys [db-spec otype oname behavior options]} statement] (if (= otype :index) (join \space "DROP INDEX" (as-identifier db-spec oname) "ON" (as-identifier db-spec (:tname options) (:schema db-spec))) (join \space "DROP" (as-sql-keyword otype) (as-identifier db-spec oname (:schema db-spec)) (when (and behavior (#{:table} otype)) [(as-sql-keyword behavior)]))))) | |||||||||||||||||||||||||
(defmethod compile [:mysql AlterDropAction] [action] (let [{:keys [db-spec element]} action is-unique (instance? UniqueConstraintDefinition element) is-pkey (and is-unique (= (:type element) :primary-key))] (join \space "DROP" (cond (instance? ColumnDefinition element) "COLUMN" (instance? ForeignKeyConstraintDefinition element) "FOREIGN KEY" (and is-unique (= (:ctype element) :unique)) "INDEX" is-pkey "PRIMARY KEY") (when-not is-pkey (as-identifier db-spec (:cname element)))))) | |||||||||||||||||||||||||
(defmethod compile [:mysql AlterRenameAction] [action] (let [{:keys [db-spec element]} action old-name (:cname element) new-name (:others element) column (with-db-meta db-spec (assoc (analyze-column (:sname element) (:tname element) old-name) :cname new-name))] (join \space "CHANGE" (as-identifier db-spec old-name) (compile (build-definition column db-spec))))) | |||||||||||||||||||||||||
Compiler implementation for PostgreSQL. | (ns lobos.backends.postgresql (:refer-clojure :exclude [compile defonce]) (:require (lobos [schema :as schema])) (:use lobos.analyzer lobos.compiler lobos.utils) (:import (lobos.ast AlterRenameAction ColumnDefinition DataTypeClause) (lobos.schema DataType Schema))) | ||||||||||||||||||||||||
Analyzer | |||||||||||||||||||||||||
(def ^{:private true} analyzer-data-type-aliases {:bool :boolean :bpchar :char :bytea :blob :float4 :real :float8 :double :int2 :smallint :int4 :integer :int8 :bigint :text :nclob :timestamptz :timestamp :timetz :time}) | |||||||||||||||||||||||||
(defmethod analyze [:postgresql DataType] [_ column-meta] (let [dtype (-> column-meta :type_name as-keyword) options {:time-zone (#{:timetz :timestamptz} dtype)} dtype (first (replace analyzer-data-type-aliases [dtype]))] (schema/data-type dtype (analyze-data-type-args dtype column-meta) options))) | |||||||||||||||||||||||||
(defmethod analyze [:postgresql Schema] [_ sname] (analyze [:lobos.analyzer/standard Schema] (or sname :public))) | |||||||||||||||||||||||||
Compiler | |||||||||||||||||||||||||
(def ^{:private true} compiler-data-type-aliases {:blob :bytea :clob :text :double :double-precision :nclob :text :nvarchar :varchar}) | |||||||||||||||||||||||||
(defmethod compile [:postgresql DataTypeClause] [expression] (let [{:keys [dtype args options]} expression {:keys [time-zone]} options dtype (first (replace compiler-data-type-aliases [dtype])) args (if (#{:bytea :text} dtype) [] args)] (unsupported (#{:binary :varbinary} dtype) "Use blob instead.") (join \space (str (as-sql-keyword dtype) (as-list args)) (when time-zone "WITH TIME ZONE")))) | |||||||||||||||||||||||||
(defmethod compile [:postgresql ColumnDefinition] [definition] (let [{:keys [db-spec cname data-type default auto-inc not-null others]} definition] (apply join \space (as-identifier db-spec cname) (if auto-inc "SERIAL" (compile data-type)) (when default (str "DEFAULT " (compile default))) (when not-null "NOT NULL") others))) | |||||||||||||||||||||||||
(defmethod compile [:postgresql AlterRenameAction] [action] (let [{:keys [db-spec element]} action old-name (:cname element) new-name (:others element)] (format "RENAME COLUMN %s TO %s" (as-identifier db-spec old-name) (as-identifier db-spec new-name)))) | |||||||||||||||||||||||||
Compiler implementation for SQLite. | (ns lobos.backends.sqlite (:refer-clojure :exclude [compile defonce]) (:require (lobos [schema :as schema])) (:use (lobos analyzer compiler connectivity internal metadata utils)) (:import (lobos.ast AlterTableStatement AutoIncClause CreateSchemaStatement CreateTableStatement DataTypeClause DropStatement IdentifierExpression) (lobos.schema DataType ForeignKeyConstraint Schema UniqueConstraint))) | ||||||||||||||||||||||||
Analyzer | |||||||||||||||||||||||||
(def ^{:private true} analyzer-data-type-aliases {:time-with-time-zone :time :timestamp-with-time-zone :timestamp}) | |||||||||||||||||||||||||
(defmethod analyze [:sqlite DataType] [_ column-meta] (let [dtype (-> column-meta :type_name as-keyword) tz? #{:time-with-time-zone :timestamp-with-time-zone} [dtype options] (if (tz? dtype) [dtype {:time-zone true}] [dtype nil]) dtype (first (replace analyzer-data-type-aliases [dtype])) args (analyze-data-type-args dtype column-meta)] (schema/data-type dtype (if (#{:decimal :numeric} dtype) [(first args)] args) options))) | |||||||||||||||||||||||||
(defmethod analyze [:sqlite UniqueConstraint] [_ sname tname cname index-meta] (let [columns (vec (map #(-> % :column_name keyword) index-meta))] (UniqueConstraint. (make-index-name tname :unique columns) :unique columns))) | |||||||||||||||||||||||||
(defn- analyze-primary-keys [tname] (let [columns (reduce #(conj %1 (-> %2 :column_name keyword)) [] (resultset-seq (.getPrimaryKeys (db-meta) nil nil (name tname))))] (when (not-empty columns) [(UniqueConstraint. (make-index-name tname :primary-key columns) :primary-key columns)]))) | |||||||||||||||||||||||||
(defn- analyze-foreign-keys [tname] (let [fks (group-by :id (try (raw-query (format "pragma foreign_key_list(%s);" (name tname))) (catch Exception _)))] (for [fk fks] (let [fk (second fk) pcolumns (reduce #(conj %1 (-> %2 :to keyword)) [] fk) fcolumns (reduce #(conj %1 (-> %2 :from keyword)) [] fk) fk (first fk) ptable (keyword (:table fk)) match (as-keyword (:match fk)) match (when-not (= match :none) match) on-delete (as-keyword (:on_delete fk)) on-delete (when-not (= on-delete :no-action) on-delete) on-delete (when on-delete [:on-delete on-delete]) on-update (as-keyword (:on_update fk)) on-update (when-not (= on-update :no-action) on-update) on-update (when on-delete [:on-update on-update])] (ForeignKeyConstraint. (make-index-name tname :fkey fcolumns) fcolumns ptable pcolumns match (into {} [on-delete on-update])))))) | |||||||||||||||||||||||||
(defn- analyze-unique [sname tname] (map (fn [[cname meta]] (analyze UniqueConstraint sname tname cname meta)) (indexes-meta sname tname #(let [nu (:non_unique %)] (or (false? nu) (= nu 0)))))) | |||||||||||||||||||||||||
(defmethod analyze [:sqlite :constraints] [_ sname tname] (concat (analyze-unique sname tname) (analyze-primary-keys tname) (analyze-foreign-keys tname))) | |||||||||||||||||||||||||
(defmethod analyze [:sqlite Schema] [_ sname] (let [db-spec (db-meta-spec) sname (or sname (->> db-spec :subname (re-find #"^\./(.*)\.\w+$") second))] (analyze [:lobos.analyzer/standard Schema] sname))) | |||||||||||||||||||||||||
Compiler | |||||||||||||||||||||||||
(defmethod compile [:sqlite IdentifierExpression] [identifier] (as-str (:name identifier))) | |||||||||||||||||||||||||
(defmethod compile [:sqlite DataTypeClause] [expression] (let [{:keys [dtype args options]} expression {:keys [collate time-zone]} options] (unsupported (and (#{:decimal :numeric} dtype) (= (count args) 2)) "Doesn't support scale argument.") (join \space (str (as-sql-keyword dtype) (as-list args)) (when collate (str "COLLATE " (as-str collate))) (when time-zone "WITH TIME ZONE")))) | |||||||||||||||||||||||||
(defmethod compile [:sqlite AutoIncClause] [_] nil) | |||||||||||||||||||||||||
(defmethod compile [:sqlite CreateSchemaStatement] [statement] (let [{:keys [db-spec sname elements]} statement] (map compile elements))) | |||||||||||||||||||||||||
(defn- drop-schema [db-spec] (map (comp compile #(schema/build-drop-statement % :cascade db-spec) #(schema/table %) :name) (with-connection db-spec (raw-query (format "select name from sqlite_master where type <> 'index';"))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlite DropStatement] [statement] (let [{:keys [db-spec otype oname behavior]} statement] (if (= otype :schema) (drop-schema db-spec) (join \space "DROP" (as-sql-keyword otype) (as-identifier db-spec oname))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlite AlterTableStatement] [statement] (unsupported "Alter statement unsupported.")) | |||||||||||||||||||||||||
Compiler implementation for SQL Server. | (ns lobos.backends.sqlserver (:refer-clojure :exclude [compile defonce]) (:require clojure.string (lobos [schema :as schema])) (:use lobos.analyzer lobos.compiler lobos.metadata lobos.utils) (:import (lobos.ast AlterAddAction AlterDropAction AlterModifyAction AlterRenameAction AlterTableStatement AutoIncClause DataTypeClause DropStatement FunctionExpression IdentifierExpression) (lobos.schema DataType Expression Schema))) | ||||||||||||||||||||||||
Analyzer | |||||||||||||||||||||||||
(defmethod analyze [:microsoft-sql-server Expression] [_ expr] (when expr (Expression. (cond (re-find #"^\(\((\d+)\)\)$" expr) (let [[[_ n]] (re-seq #"(\w+)(\(\))?" expr)] (Integer/parseInt n)))))) | |||||||||||||||||||||||||
(def ^{:private true} analyzer-data-type-aliases {:bit :boolean :datetime2 :timestamp :image :blob :int :integer :ntext :nclob :text :clob}) | |||||||||||||||||||||||||
(defmethod analyze [:microsoft-sql-server DataType] [_ column-meta] (let [dtype (-> column-meta :type_name as-keyword) [dtype options] (if (= dtype :datetimeoffset) [:timestamp {:time-zone true}] [dtype nil]) dtype (first (replace analyzer-data-type-aliases [dtype]))] (schema/data-type dtype (analyze-data-type-args dtype column-meta) options))) | |||||||||||||||||||||||||
(defmethod analyze [:microsoft-sql-server Schema] [_ sname] (analyze [:lobos.analyzer/standard Schema] (or sname :dbo))) | |||||||||||||||||||||||||
Compiler | |||||||||||||||||||||||||
(defmethod compile [:sqlserver IdentifierExpression] [identifier] (let [{:keys [db-spec name qualifiers]} identifier] (join* \. (->> (concat qualifiers [name]) (filter identity) (map #(when % (as-str \[ % \]))))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver FunctionExpression] [function] (let [{:keys [db-spec name args]} function name (if (= name :length) :len name)] (str (as-sql-keyword name) (as-list (map compile args))))) | |||||||||||||||||||||||||
(def ^{:private true} compiler-data-type-aliases {:blob :image :boolean :bit :clob :text :double :float :nclob :ntext :timestamp :datetime2}) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver DataTypeClause] [expression] (let [{:keys [dtype args options]} expression {:keys [collate time-zone]} options dtype (if (and (= dtype :timestamp) time-zone) :datetimeoffset (first (replace compiler-data-type-aliases [dtype]))) args (if (#{:image :ntext :text} dtype) [] args)] (unsupported (and (= dtype :time) time-zone) "Time zone unsupported for time data type.") (join \space (str (as-sql-keyword dtype) (as-list args)) (when collate (str "COLLATE " (as-str collate)))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver AutoIncClause] [_] "IDENTITY") | |||||||||||||||||||||||||
(defn- drop-schema-cascade [db-spec sname] (vec (for [element (with-db-meta db-spec (-> (analyze-schema sname) :elements keys))] (compile (schema/build-drop-statement (schema/table element) :cascade (assoc db-spec :schema sname)))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver DropStatement] [statement] (let [{:keys [db-spec otype oname behavior options]} statement sql-string (join \space "DROP" (as-sql-keyword otype) (as-identifier db-spec oname (when (not= otype :schema) (:schema db-spec))))] (if (= otype :index) (join \space "DROP INDEX" (as-identifier db-spec oname) "ON" (as-identifier db-spec (:tname options) (:schema db-spec))) (apply join \; (conj (when (and (= otype :schema) (= behavior :cascade)) (drop-schema-cascade db-spec oname)) sql-string))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver AlterModifyAction] [action] (let [{:keys [db-spec element]} action default (:default element) cname (when default (make-index-name (:tname element) "default" (list (:cname element))))] (cond (= default :drop) (join \space "DROP CONSTRAINT" (as-identifier db-spec cname)) default (join \space "ADD CONSTRAINT" (as-identifier db-spec cname) "DEFAULT" (compile default) "FOR" (as-identifier db-spec (:cname element))) :else (unsupported "Only set/drop default supported.")))) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver AlterRenameAction] [action] (let [{:keys [db-spec element]} action] (format "EXEC sp_rename '%s', '%s', 'COLUMN';" (join \. (as-identifier db-spec (:sname element)) (as-identifier db-spec (:tname element)) (as-identifier db-spec (:cname element))) (as-str (:others element))))) | |||||||||||||||||||||||||
(defmethod compile [:sqlserver AlterTableStatement] [statement] (let [{:keys [db-spec tname action element]} statement element (assoc element :sname (:schema db-spec) :tname tname)] (if (= action :rename) (compile (AlterRenameAction. db-spec element)) (join \space "ALTER TABLE" (as-identifier db-spec tname (:schema db-spec)) (case action :add (compile (AlterAddAction. db-spec element)) :drop (compile (AlterDropAction. db-spec element)) :modify (compile (AlterModifyAction. db-spec element))))))) | |||||||||||||||||||||||||
Abstract SQL syntax tree for the DDL part of the language. | (ns lobos.ast) | ||||||||||||||||||||||||
Special Records | |||||||||||||||||||||||||
(defrecord Mode [db-spec]) | |||||||||||||||||||||||||
Expression Records | |||||||||||||||||||||||||
(defrecord ScalarExpression [db-spec scalar]) | |||||||||||||||||||||||||
(defrecord IdentifierExpression [db-spec name qualifiers]) | |||||||||||||||||||||||||
(defrecord FunctionExpression [db-spec name args]) | |||||||||||||||||||||||||
(defrecord OperatorExpression [db-spec op left right]) | |||||||||||||||||||||||||
Clause Records | |||||||||||||||||||||||||
(defrecord AutoIncClause [db-spec]) | |||||||||||||||||||||||||
(defrecord DataTypeClause [db-spec dtype args options]) | |||||||||||||||||||||||||
Definition Records | |||||||||||||||||||||||||
(defrecord ColumnDefinition [db-spec cname data-type default auto-inc not-null others]) | |||||||||||||||||||||||||
(defrecord ConstraintDefinition [db-spec cname]) | |||||||||||||||||||||||||
(defrecord UniqueConstraintDefinition [db-spec cname ctype columns]) | |||||||||||||||||||||||||
(defrecord ForeignKeyConstraintDefinition [db-spec cname columns parent-table parent-columns match triggered-actions]) | |||||||||||||||||||||||||
(defrecord CheckConstraintDefinition [db-spec cname condition]) | |||||||||||||||||||||||||
Statement Records | |||||||||||||||||||||||||
(defrecord CreateSchemaStatement [db-spec sname elements]) | |||||||||||||||||||||||||
(defrecord CreateTableStatement [db-spec tname elements]) | |||||||||||||||||||||||||
(defrecord CreateIndexStatement [db-spec iname tname columns options]) | |||||||||||||||||||||||||
(defrecord DropStatement [db-spec otype oname behavior options]) | |||||||||||||||||||||||||
(defrecord AlterTableStatement [db-spec tname action element]) | |||||||||||||||||||||||||
Alter Action Records | |||||||||||||||||||||||||
(defrecord AlterAddAction [db-spec element]) | |||||||||||||||||||||||||
(defrecord AlterDropAction [db-spec element]) | |||||||||||||||||||||||||
(defrecord AlterModifyAction [db-spec element]) | |||||||||||||||||||||||||
(defrecord AlterRenameAction [db-spec element]) | |||||||||||||||||||||||||
Helpers | |||||||||||||||||||||||||
Import all expression AST records into the calling namespace. | (defn import-expressions [] (import '(lobos.ast ScalarExpression IdentifierExpression FunctionExpression OperatorExpression))) | ||||||||||||||||||||||||
Import all clause AST records into the calling namespace. | (defn import-clauses [] (import '(lobos.ast AutoIncClause DataTypeClause))) | ||||||||||||||||||||||||
Import all definition AST records into the calling namespace. | (defn import-definitions [] (import '(lobos.ast ColumnDefinition ConstraintDefinition UniqueConstraintDefinition ForeignKeyConstraintDefinition CheckConstraintDefinition))) | ||||||||||||||||||||||||
Import all statement AST records into the calling namespace. | (defn import-statements [] (import '(lobos.ast CreateSchemaStatement CreateTableStatement CreateIndexStatement DropStatement AlterTableStatement))) | ||||||||||||||||||||||||
Import all action AST records into the calling namespace. | (defn import-actions [] (import '(lobos.ast AlterAddAction AlterDropAction AlterModifyAction AlterRenameAction))) | ||||||||||||||||||||||||
Import all AST records into the calling namespace. | (defn import-all [] (import '(lobos.ast Mode)) (import-expressions) (import-clauses) (import-definitions) (import-statements) (import-actions)) | ||||||||||||||||||||||||