diff --git a/docs/postgresql.md b/docs/postgresql.md new file mode 100644 index 00000000..ef46060e --- /dev/null +++ b/docs/postgresql.md @@ -0,0 +1,18 @@ +# Postgresql + +In Postgres field names are case-sensitive. If this is a pure Crystal project then this does not matter as all variable and table names will be snake_case. + +However, if you need to integrate your Crystal project with another, pre-existing technology (such as a NodeJS application), then you might find that column or table names are now in camelCase. + +As such you can create a class method which overrides the Granite default naming convention: + +```crystal +class MyModel < Granite::Base + + + def self.table_name + "\"schemaName\".\"TableName\"" + end + +end +``` diff --git a/docs/readme.md b/docs/readme.md index f1533a0a..75f1f001 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -72,3 +72,5 @@ end [Migrations](./migrations.md) [Imports](./imports.md) + +[Postgresql](./postgresql.md) diff --git a/src/adapter/base.cr b/src/adapter/base.cr index aa0395e7..a9cb1a48 100644 --- a/src/adapter/base.cr +++ b/src/adapter/base.cr @@ -60,7 +60,7 @@ abstract class Granite::Adapter::Base # Returns `true` if a record exists that matches *criteria*, otherwise `false`. def exists?(table_name : String, criteria : String, params = [] of Granite::Columns::Type) : Bool - statement = "SELECT EXISTS(SELECT 1 FROM #{table_name} WHERE #{ensure_clause_template(criteria)})" + statement = "SELECT EXISTS(SELECT 1 FROM #{quote(table_name)} WHERE #{ensure_clause_template(criteria)})" exists = false elapsed_time = Time.measure do @@ -106,10 +106,15 @@ abstract class Granite::Adapter::Base macro inherited # quotes table and column names def quote(name : String) : String - String.build do |str| - str << QUOTING_CHAR - str << name - str << QUOTING_CHAR + # only quote the string if it isn't already quoted + if name[0] != QUOTING_CHAR && name[name.size - 1] != QUOTING_CHAR + String.build do |str| + str << QUOTING_CHAR + str << name + str << QUOTING_CHAR + end + else + name end end diff --git a/src/granite/association_collection.cr b/src/granite/association_collection.cr index c7abfbc4..940206ba 100644 --- a/src/granite/association_collection.cr +++ b/src/granite/association_collection.cr @@ -36,7 +36,7 @@ class Granite::AssociationCollection(Owner, Target) private def query if through.nil? - "WHERE #{Target.table_name}.#{@foreign_key} = ?" + "WHERE #{Target.table_name}.#{Target.quote(@foreign_key.to_s)} = ?" else "JOIN #{through} ON #{through}.#{Target.to_s.underscore}_id = #{Target.table_name}.#{Target.primary_name} " \ "WHERE #{through}.#{@foreign_key} = ?" diff --git a/src/granite/query/assemblers/base.cr b/src/granite/query/assemblers/base.cr index 59a48a68..0606bd56 100644 --- a/src/granite/query/assemblers/base.cr +++ b/src/granite/query/assemblers/base.cr @@ -36,6 +36,23 @@ module Granite::Query::Assembler clauses.compact!.join " " end + # Use macro in order to read a constant defined in each subclasses. + macro inherited + # quotes table and column names + def quote(name : String) : String + # only quote the string if it isn't already quoted + if name[0] != QUOTING_CHAR && name[name.size - 1] != QUOTING_CHAR + String.build do |str| + str << QUOTING_CHAR + str << name + str << QUOTING_CHAR + end + else + name + end + end + end + def where return @where if @where @@ -60,7 +77,7 @@ module Granite::Query::Assembler add_aggregate_field expression[:field] if expression[:value].nil? - clauses << "#{expression[:field]} IS NULL" + clauses << "#{quote(expression[:field])} IS NULL" elsif expression[:value].is_a?(Array) in_stmt = String.build do |str| str << '(' @@ -72,9 +89,9 @@ module Granite::Query::Assembler end str << ')' end - clauses << "#{expression[:field]} #{sql_operator(expression[:operator])} #{in_stmt}" + clauses << "#{quote(expression[:field])} #{sql_operator(expression[:operator])} #{in_stmt}" else - clauses << "#{expression[:field]} #{sql_operator(expression[:operator])} #{add_parameter expression[:value]}" + clauses << "#{quote(expression[:field])} #{sql_operator(expression[:operator])} #{add_parameter expression[:value]}" end end end @@ -101,9 +118,9 @@ module Granite::Query::Assembler add_aggregate_field expression[:field] if expression[:direction] == Builder::Sort::Ascending - "#{expression[:field]} ASC" + "#{quote(expression[:field])} ASC" else - "#{expression[:field]} DESC" + "#{quote(expression[:field])} DESC" end end @@ -115,7 +132,7 @@ module Granite::Query::Assembler group_fields = @query.group_fields return nil if group_fields.none? group_clauses = group_fields.map do |expression| - "#{expression[:field]}" + "#{quote(expression[:field])}" end @group_by = "GROUP BY #{group_clauses.join ", "}" diff --git a/src/granite/query/assemblers/mysql.cr b/src/granite/query/assemblers/mysql.cr index 65897a82..ccd03ef3 100644 --- a/src/granite/query/assemblers/mysql.cr +++ b/src/granite/query/assemblers/mysql.cr @@ -2,6 +2,7 @@ # This will likely require adapter specific subclassing :[. module Granite::Query::Assembler class Mysql(Model) < Base(Model) + QUOTING_CHAR = '`' @placeholder = "?" def add_parameter(value : Granite::Columns::Type) : String diff --git a/src/granite/query/assemblers/pg.cr b/src/granite/query/assemblers/pg.cr index db3a43c4..1f94d0a9 100644 --- a/src/granite/query/assemblers/pg.cr +++ b/src/granite/query/assemblers/pg.cr @@ -2,8 +2,15 @@ # This will likely require adapter specific subclassing :[. module Granite::Query::Assembler class Pg(Model) < Base(Model) + QUOTING_CHAR = '"' @placeholder = "$" + def field_list + # Override this method to quote the fields as upper case characters + # get converted to lower case in PG, which we do not want. + [Model.fields].flatten.map{ |field| "#{quote(field)}" }.join ", " + end + def add_parameter(value : Granite::Columns::Type) : String @numbered_parameters << value "$#{@numbered_parameters.size}" diff --git a/src/granite/query/assemblers/sqlite.cr b/src/granite/query/assemblers/sqlite.cr index 2f9bd5d1..48fb117a 100644 --- a/src/granite/query/assemblers/sqlite.cr +++ b/src/granite/query/assemblers/sqlite.cr @@ -1,5 +1,6 @@ module Granite::Query::Assembler class Sqlite(Model) < Base(Model) + QUOTING_CHAR = '"' @placeholder = "?" def add_parameter(value : Granite::Columns::Type) : String