Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable non snake-case naming in Postgres #445

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
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