Skip to content

Commit

Permalink
Merge pull request #495 from circulon/patch/fix_join_on_value
Browse files Browse the repository at this point in the history
  • Loading branch information
josephmancuso committed Sep 15, 2021
2 parents 2811a55 + 68821f5 commit f6fe981
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 134 deletions.
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -67,6 +67,7 @@
"masoniteorm.connections",
"masoniteorm.expressions",
"masoniteorm.factories",
"masoniteorm.helpers",
"masoniteorm.migrations",
"masoniteorm.models",
"masoniteorm.observers",
Expand Down
89 changes: 65 additions & 24 deletions src/masoniteorm/expressions/expressions.py
@@ -1,5 +1,7 @@
import inspect

from ..helpers.misc import deprecated


class QueryExpression:
"""A helper class to manage query expressions."""
Expand Down Expand Up @@ -147,7 +149,6 @@ def __init__(self, table, clause="join"):
self.alias = None
self.clause = clause
self.on_clauses = []
self.where_clauses = []

if " as " in self.table:
self.table = table.split(" as ")[0]
Expand All @@ -161,49 +162,73 @@ def or_on(self, column1, equality, column2):
self.on_clauses.append(OnClause(column1, equality, column2, "or"))
return self

def where(self, column, *args):
"""Specifies a where expression.
def on_value(self, column, *args):
equality, value = self._extract_operator_value(*args)
self.on_clauses += ((OnValueClause(column, equality, value, "value")),)
return self

Arguments:
column {string} -- The name of the column to search
def or_on_value(self, column, *args):
equality, value = self._extract_operator_value(*args)
self.on_clauses += (
(OnValueClause(column, equality, value, "value", operator="or")),
)
return self

Keyword Arguments:
args {List} -- The operator and the value of the column to search. (default: {None})
def on_null(self, column):
"""Specifies an ON expression where the column IS NULL.
Arguments:
column {string} -- The name of the column.
Returns:
self
"""
operator, value = self._extract_operator_value(*args)
self.on_clauses += ((OnValueClause(column, "=", None, "NULL")),)
return self

def on_not_null(self, column: str):
"""Specifies an ON expression where the column IS NOT NULL.
Arguments:
column {string} -- The name of the column.
self.where_clauses += ((QueryExpression(column, operator, value, "value")),)
Returns:
self
"""
self.on_clauses += ((OnValueClause(column, "=", True, "NOT NULL")),)
return self

def where_null(self, column):
"""Specifies a where expression where the column is NULL.
def or_on_null(self, column):
"""Specifies an ON expression where the column IS NULL.
Arguments:
column {string} -- The name of the column.
Returns:
self
"""
self.where_clauses += ((QueryExpression(column, "=", None, "NULL")),)
self.on_clauses += ((OnValueClause(column, "=", None, "NULL", operator="or")),)
return self

def where_not_null(self, column: str):
"""Specifies a where expression where the column is not NULL.
def or_on_not_null(self, column: str):
"""Specifies an ON expression where the column IS NOT NULL.
Arguments:
column {string} -- The name of the column.
Returns:
self
"""
self._wheres += ((QueryExpression(column, "=", True, "NOT NULL")),)
self.on_clauses += (
(OnValueClause(column, "=", True, "NOT NULL", operator="or")),
)
return self

def _extract_operator_value(self, *args):
@deprecated("Using where() in a Join clause has been superceded by on_value()")
def where(self, column, *args):
return self.on_value(column, *args)

def _extract_operator_value(self, *args):
operators = ["=", ">", ">=", "<", "<=", "!=", "<>", "like", "not like"]

operator = operators[0]
Expand All @@ -224,21 +249,37 @@ def _extract_operator_value(self, *args):

return operator, value

def where(self, column, *args):
operator, value = self._extract_operator_value(*args)
self.where_clauses.append(QueryExpression(column, operator, value, "value"))
return self

def get_on_clauses(self):
return self.on_clauses

def get_where_clauses(self):
return self.where_clauses


class OnClause:
def __init__(self, column1, equality, column2, operator="and"):
self.column1 = column1
self.column2 = column2
self.equality = equality
self.operator = operator


class OnValueClause:
"""A helper class to manage ON expressions in joins with a value."""

def __init__(
self,
column,
equality,
value,
value_type="value",
keyword=None,
raw=False,
bindings=(),
operator="and",
):
self.column = column
self.equality = equality
self.value = value
self.value_type = value_type
self.keyword = keyword
self.raw = raw
self.bindings = bindings
self.operator = operator
20 changes: 0 additions & 20 deletions src/masoniteorm/helpers.py

This file was deleted.

Empty file.
45 changes: 45 additions & 0 deletions src/masoniteorm/helpers/misc.py
@@ -0,0 +1,45 @@
"""Module for miscellaneous helper methods."""

import warnings
import re


def deprecated(message):
warnings.simplefilter("default", DeprecationWarning)

def deprecated_decorator(func):
def deprecated_func(*args, **kwargs):
warnings.warn(
"{} is a deprecated function. {}".format(func.__name__, message),
category=DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)

return deprecated_func

return deprecated_decorator


def database_url(url):
regex = re.compile(
"(?P<schema>.*?)://(?P<user>.*?):(?P<password>.*?)@(?P<host>.*?)/(?P<database>.*)"
)
dic = {}
match = regex.match(url)
user = match.group(2)
host = match.group(4)
hostname = host.split(":")[0]
port = None if ":" not in host else host.split(":")[1]
database = match.group(5)
dic.update(
{
"user": user,
"password": match.group(3),
"host": hostname,
"port": port,
"database": database,
}
)

return dic
62 changes: 25 additions & 37 deletions src/masoniteorm/query/grammars/BaseGrammar.py
Expand Up @@ -6,6 +6,7 @@
SelectExpression,
BetweenExpression,
JoinClause,
OnClause,
)


Expand Down Expand Up @@ -81,13 +82,13 @@ def _compile_select(self, qmark=False):
.format(
columns=self.process_columns(separator=", ", qmark=qmark),
table=self.process_table(self.table),
joins=self.process_joins(qmark=qmark),
wheres=self.process_wheres(qmark=qmark),
limit=self.process_limit(),
offset=self.process_offset(),
aggregates=self.process_aggregates(),
order_by=self.process_order_by(),
group_by=self.process_group_by(),
joins=self.process_joins(qmark=qmark),
having=self.process_having(),
lock=self.process_locks(),
)
Expand All @@ -99,13 +100,13 @@ def _compile_select(self, qmark=False):
.format(
columns=self.process_columns(separator=", ", qmark=qmark),
table=self.process_table(self.table),
joins=self.process_joins(qmark=qmark),
wheres=self.process_wheres(qmark=qmark),
limit=self.process_limit(),
offset=self.process_offset(),
aggregates=self.process_aggregates(),
order_by=self.process_order_by(),
group_by=self.process_group_by(),
joins=self.process_joins(qmark=qmark),
having=self.process_having(),
lock=self.process_locks(),
)
Expand Down Expand Up @@ -241,49 +242,36 @@ def process_joins(self, qmark=False):
for join in self._joins:
if isinstance(join, JoinClause):
on_string = ""
where_string = ""
cause_loop = 1
for clause in join.get_on_clauses():
if cause_loop == 1:
keyword = "ON"
else:
keyword = clause.operator.upper()

on_string += f"{keyword} {self._table_column_string(clause.column1)} {clause.equality} {self._table_column_string(clause.column2)} "
cause_loop += 1
for clause_idx, clause in enumerate(join.get_on_clauses()):
keyword = clause.operator.upper() if clause_idx else "ON"

where_loop = 1

for clause in join.get_where_clauses():
if where_loop == 1:
keyword = "WHERE"
else:
keyword = "AND"

if clause.value_type == "NULL":
sql_string = self.where_null_string()
where_string += sql_string.format(
keyword=keyword, column=self.process_column(clause.column)
)
elif clause.value_type == "NOT NULL":
sql_string = self.where_not_null_string()
where_string += sql_string.format(
keyword=keyword, column=self.process_column(clause.column)
)
if isinstance(clause, OnClause):
on_string += f"{keyword} {self._table_column_string(clause.column1)} {clause.equality} {self._table_column_string(clause.column2)} "
else:
if qmark:
value = "'?'"
self.add_binding(clause.value)
if clause.value_type == "NULL":
sql_string = self.where_null_string()
on_string += sql_string.format(
keyword=keyword,
column=self.process_column(clause.column),
)
elif clause.value_type == "NOT NULL":
sql_string = self.where_not_null_string()
on_string += sql_string.format(
keyword=keyword,
column=self.process_column(clause.column),
)
else:
value = self._compile_value(clause.value)
where_string += f"{keyword} {self.process_column(clause.column)} {clause.equality} {value} "
where_loop += 1
if qmark:
value = "'?'"
self.add_binding(clause.value)
else:
value = self._compile_value(clause.value)
on_string += f"{keyword} {self._table_column_string(clause.column)} {clause.equality} {value} "

sql += self.join_string().format(
foreign_table=self.process_table(join.table),
alias=f" AS {self.process_table(join.alias)}" if join.alias else "",
on=on_string,
wheres=f" {where_string}",
keyword=self.join_keywords[join.clause],
)
sql += " "
Expand Down
6 changes: 3 additions & 3 deletions src/masoniteorm/query/grammars/MSSQLGrammar.py
Expand Up @@ -90,7 +90,7 @@ def additional_where_string(self):
return "AND"

def join_string(self):
return "{keyword} {foreign_table}{alias} {on}{wheres}"
return "{keyword} {foreign_table}{alias} {on}"

def aggregate_string(self):
return "{aggregate_function}({column}) AS {alias}"
Expand All @@ -114,7 +114,7 @@ def value_equal_string(self):
return "{keyword} {value1} = {value2}"

def where_null_string(self):
return "{keyword} {column} IS NULL"
return " {keyword} {column} IS NULL"

def between_string(self):
return "{keyword} {column} BETWEEN {low} AND {high}"
Expand All @@ -123,7 +123,7 @@ def not_between_string(self):
return "{keyword} {column} NOT BETWEEN {low} AND {high}"

def where_not_null_string(self):
return "{keyword} {column} IS NOT NULL"
return " {keyword} {column} IS NOT NULL"

def where_string(self):
return " {keyword} {column} {equality} {value}"
Expand Down
4 changes: 2 additions & 2 deletions src/masoniteorm/query/grammars/MySQLGrammar.py
Expand Up @@ -167,7 +167,7 @@ def value_string(self):
return "'{value}'{separator}"

def join_string(self):
return "{keyword} {foreign_table}{alias} {on}{wheres}"
return "{keyword} {foreign_table}{alias} {on}"

def limit_string(self, offset=False):
return "LIMIT {limit}"
Expand Down Expand Up @@ -200,7 +200,7 @@ def having_equality_string(self):
return "HAVING {column} {equality} {value}"

def where_null_string(self):
return "{keyword} {column} IS NULL"
return " {keyword} {column} IS NULL"

def where_not_null_string(self):
return " {keyword} {column} IS NOT NULL"
Expand Down
4 changes: 2 additions & 2 deletions src/masoniteorm/query/grammars/PostgresGrammar.py
Expand Up @@ -156,7 +156,7 @@ def value_string(self):
return "'{value}'{separator}"

def join_string(self):
return "{keyword} {foreign_table}{alias} {on}{wheres}"
return "{keyword} {foreign_table}{alias} {on}"

def limit_string(self, offset=False):
return "LIMIT {limit}"
Expand Down Expand Up @@ -189,7 +189,7 @@ def having_equality_string(self):
return "HAVING {column} {equality} {value}"

def where_null_string(self):
return "{keyword} {column} IS NULL"
return " {keyword} {column} IS NULL"

def where_not_null_string(self):
return " {keyword} {column} IS NOT NULL"
Expand Down

0 comments on commit f6fe981

Please sign in to comment.