Skip to content
18 changes: 18 additions & 0 deletions docs/postgresql.md
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 2 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@ end
[Migrations](./migrations.md)

[Imports](./imports.md)

[Postgresql](./postgresql.md)
15 changes: 10 additions & 5 deletions src/adapter/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/granite/association_collection.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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} = ?"
Expand Down
29 changes: 23 additions & 6 deletions src/granite/query/assemblers/base.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 << '('
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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 ", "}"
Expand Down
1 change: 1 addition & 0 deletions src/granite/query/assemblers/mysql.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/granite/query/assemblers/pg.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
1 change: 1 addition & 0 deletions src/granite/query/assemblers/sqlite.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Granite::Query::Assembler
class Sqlite(Model) < Base(Model)
QUOTING_CHAR = '"'
@placeholder = "?"

def add_parameter(value : Granite::Columns::Type) : String
Expand Down