Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
5c748ddb
Commit
5c748ddb
authored
Apr 29, 2014
by
Carsten Brandt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
added case insensitve LIKE to PostgresQueryBuilder
fixes #3252 also improved unit tests for querybuilder buildLikeCondition
parent
f182504a
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
154 additions
and
37 deletions
+154
-37
db-query-builder.md
docs/guide/db-query-builder.md
+14
-0
CHANGELOG.md
framework/CHANGELOG.md
+1
-0
QueryBuilder.php
framework/db/QueryBuilder.php
+31
-31
QueryBuilder.php
framework/db/pgsql/QueryBuilder.php
+24
-1
QueryBuilderTest.php
tests/unit/framework/db/QueryBuilderTest.php
+58
-5
PostgreSQLQueryBuilderTest.php
tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php
+26
-0
No files found.
docs/guide/db-query-builder.md
View file @
5c748ddb
...
...
@@ -195,12 +195,16 @@ Operator can be one of the following:
it will be converted into a string using the rules described here. For example,
`['and', 'type=1', ['or', 'id=1', 'id=2']]`
will generate
`type=1 AND (id=1 OR id=2)`
.
The method will NOT do any quoting or escaping.
-
`or`
: similar to the
`and`
operator except that the operands are concatenated using
`OR`
.
-
`between`
: operand 1 should be the column name, and operand 2 and 3 should be the
starting and ending values of the range that the column is in.
For example,
`['between', 'id', 1, 10]`
will generate
`id BETWEEN 1 AND 10`
.
-
`not between`
: similar to
`between`
except the
`BETWEEN`
is replaced with
`NOT BETWEEN`
in the generated condition.
-
`in`
: operand 1 should be a column or DB expression. Operand 2 can be either an array or a
`Query`
object.
It will generate an
`IN`
condition. If Operand 2 is an array, it will represent the range of the values
that the column or DB expression should be; If Operand 2 is a
`Query`
object, a sub-query will be generated
...
...
@@ -209,7 +213,9 @@ Operator can be one of the following:
The method will properly quote the column name and escape values in the range.
The
`in`
operator also supports composite columns. In this case, operand 1 should be an array of the columns,
while operand 2 should be an array of arrays or a
`Query`
object representing the range of the columns.
-
`not in`
: similar to the
`in`
operator except that
`IN`
is replaced with
`NOT IN`
in the generated condition.
-
`like`
: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
the values that the column or DB expression should be like.
For example,
`['like', 'name', 'tester']`
will generate
`name LIKE '%tester%'`
.
...
...
@@ -222,14 +228,22 @@ Operator can be one of the following:
You may use
`false`
or an empty array to indicate the values are already escaped and no escape
should be applied. Note that when using an escape mapping (or the third operand is not provided),
the values will be automatically enclosed within a pair of percentage characters.
> Note: When using PostgreSQL you may also use [`ilike`](http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE)
> instead of `like` for case-insensitive matching.
-
`or like`
: similar to the
`like`
operator except that
`OR`
is used to concatenate the
`LIKE`
predicates when operand 2 is an array.
-
`not like`
: similar to the
`like`
operator except that
`LIKE`
is replaced with
`NOT LIKE`
in the generated condition.
-
`or not like`
: similar to the
`not like`
operator except that
`OR`
is used to concatenate
the
`NOT LIKE`
predicates.
-
`exists`
: requires one operand which must be an instance of
[
[yii\db\Query
]
] representing the sub-query.
It will build a
`EXISTS (sub-query)`
expression.
-
`not exists`
: similar to the
`exists`
operator and builds a
`NOT EXISTS (sub-query)`
expression.
If you are building parts of condition dynamically it's very convenient to use
`andWhere()`
and
`orWhere()`
:
...
...
framework/CHANGELOG.md
View file @
5c748ddb
...
...
@@ -33,6 +33,7 @@ Yii Framework 2 Change Log
-
Enh #3154: Added validation error display for
`GridView`
filters (ivan-kolmychek)
-
Enh #3222: Added
`useTablePrefix`
option to the model generator for Gii (horizons2)
-
Enh #3230: Added
`yii\filters\AccessControl::user`
to support access control with different actors (qiangxue)
-
Enh #3252: Added support for case insensitive matching using ILIKE to PostgreSQL QueryBuilder (cebe)
-
Enh: Added support for using sub-queries when building a DB query with
`IN`
condition (qiangxue)
-
Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
-
Enh: Added
`yii\web\UrlManager::addRules()`
to simplify adding new URL rules (qiangxue)
...
...
framework/db/QueryBuilder.php
View file @
5c748ddb
...
...
@@ -41,6 +41,26 @@ class QueryBuilder extends \yii\base\Object
* Child classes should override this property to declare supported type mappings.
*/
public
$typeMap
=
[];
/**
* @var array map of query condition to builder methods.
* These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
*/
protected
$conditionBuilders
=
[
'NOT'
=>
'buildNotCondition'
,
'AND'
=>
'buildAndCondition'
,
'OR'
=>
'buildAndCondition'
,
'BETWEEN'
=>
'buildBetweenCondition'
,
'NOT BETWEEN'
=>
'buildBetweenCondition'
,
'IN'
=>
'buildInCondition'
,
'NOT IN'
=>
'buildInCondition'
,
'LIKE'
=>
'buildLikeCondition'
,
'NOT LIKE'
=>
'buildLikeCondition'
,
'OR LIKE'
=>
'buildLikeCondition'
,
'OR NOT LIKE'
=>
'buildLikeCondition'
,
'EXISTS'
=>
'buildExistsCondition'
,
'NOT EXISTS'
=>
'buildExistsCondition'
,
];
/**
* Constructor.
...
...
@@ -844,39 +864,22 @@ class QueryBuilder extends \yii\base\Object
*/
public
function
buildCondition
(
$condition
,
&
$params
)
{
static
$builders
=
[
'NOT'
=>
'buildNotCondition'
,
'AND'
=>
'buildAndCondition'
,
'OR'
=>
'buildAndCondition'
,
'BETWEEN'
=>
'buildBetweenCondition'
,
'NOT BETWEEN'
=>
'buildBetweenCondition'
,
'IN'
=>
'buildInCondition'
,
'NOT IN'
=>
'buildInCondition'
,
'LIKE'
=>
'buildLikeCondition'
,
'NOT LIKE'
=>
'buildLikeCondition'
,
'OR LIKE'
=>
'buildLikeCondition'
,
'OR NOT LIKE'
=>
'buildLikeCondition'
,
'EXISTS'
=>
'buildExistsCondition'
,
'NOT EXISTS'
=>
'buildExistsCondition'
,
];
if
(
!
is_array
(
$condition
))
{
return
(
string
)
$condition
;
}
elseif
(
empty
(
$condition
))
{
return
''
;
}
if
(
isset
(
$condition
[
0
]))
{
// operator format: operator, operand 1, operand 2, ...
$operator
=
strtoupper
(
$condition
[
0
]);
if
(
isset
(
$
b
uilders
[
$operator
]))
{
$method
=
$
b
uilders
[
$operator
];
if
(
isset
(
$
this
->
conditionB
uilders
[
$operator
]))
{
$method
=
$
this
->
conditionB
uilders
[
$operator
];
array_shift
(
$condition
);
return
$this
->
$method
(
$operator
,
$condition
,
$params
);
}
else
{
throw
new
InvalidParamException
(
'Found unknown operator in query: '
.
$operator
);
}
}
else
{
// hash format: 'column1' => 'value1', 'column2' => 'value2', ...
return
$this
->
buildHashCondition
(
$condition
,
$params
);
}
}
...
...
@@ -912,7 +915,6 @@ class QueryBuilder extends \yii\base\Object
}
}
}
return
count
(
$parts
)
===
1
?
$parts
[
0
]
:
'('
.
implode
(
') AND ('
,
$parts
)
.
')'
;
}
...
...
@@ -1071,7 +1073,6 @@ class QueryBuilder extends \yii\base\Object
return
"
$column
$operator
("
.
implode
(
', '
,
$values
)
.
')'
;
}
else
{
$operator
=
$operator
===
'IN'
?
'='
:
'<>'
;
return
$column
.
$operator
.
reset
(
$values
);
}
}
...
...
@@ -1130,19 +1131,19 @@ class QueryBuilder extends \yii\base\Object
$escape
=
isset
(
$operands
[
2
])
?
$operands
[
2
]
:
[
'%'
=>
'\%'
,
'_'
=>
'\_'
,
'\\'
=>
'\\\\'
];
unset
(
$operands
[
2
]);
if
(
!
preg_match
(
'/^(AND |OR |)(((NOT |))I?LIKE)/'
,
$operator
,
$matches
))
{
throw
new
InvalidParamException
(
"Invalid operator '
$operator
'."
);
}
$andor
=
' '
.
(
!
empty
(
$matches
[
1
])
?
$matches
[
1
]
:
'AND '
);
$not
=
!
empty
(
$matches
[
3
]);
$operator
=
$matches
[
2
];
list
(
$column
,
$values
)
=
$operands
;
$values
=
(
array
)
$values
;
if
(
empty
(
$values
))
{
return
$operator
===
'LIKE'
||
$operator
===
'OR LIKE'
?
'0=1'
:
''
;
}
if
(
$operator
===
'LIKE'
||
$operator
===
'NOT LIKE'
)
{
$andor
=
' AND '
;
}
else
{
$andor
=
' OR '
;
$operator
=
$operator
===
'OR LIKE'
?
'LIKE'
:
'NOT LIKE'
;
return
$not
?
''
:
'0=1'
;
}
if
(
strpos
(
$column
,
'('
)
===
false
)
{
...
...
@@ -1171,7 +1172,6 @@ class QueryBuilder extends \yii\base\Object
{
if
(
$operands
[
0
]
instanceof
Query
)
{
list
(
$sql
,
$params
)
=
$this
->
build
(
$operands
[
0
],
$params
);
return
"
$operator
(
$sql
)"
;
}
else
{
throw
new
InvalidParamException
(
'Subquery for EXISTS operator must be a Query object.'
);
...
...
framework/db/pgsql/QueryBuilder.php
View file @
5c748ddb
...
...
@@ -17,7 +17,6 @@ use yii\base\InvalidParamException;
*/
class
QueryBuilder
extends
\yii\db\QueryBuilder
{
/**
* @var array mapping from abstract column types (keys) to physical column types (values).
*/
...
...
@@ -39,6 +38,30 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema
::
TYPE_BOOLEAN
=>
'boolean'
,
Schema
::
TYPE_MONEY
=>
'numeric(19,4)'
,
];
/**
* @var array map of query condition to builder methods.
* These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
*/
protected
$conditionBuilders
=
[
'NOT'
=>
'buildNotCondition'
,
'AND'
=>
'buildAndCondition'
,
'OR'
=>
'buildAndCondition'
,
'BETWEEN'
=>
'buildBetweenCondition'
,
'NOT BETWEEN'
=>
'buildBetweenCondition'
,
'IN'
=>
'buildInCondition'
,
'NOT IN'
=>
'buildInCondition'
,
'LIKE'
=>
'buildLikeCondition'
,
'ILIKE'
=>
'buildLikeCondition'
,
'NOT LIKE'
=>
'buildLikeCondition'
,
'NOT ILIKE'
=>
'buildLikeCondition'
,
'OR LIKE'
=>
'buildLikeCondition'
,
'OR ILIKE'
=>
'buildLikeCondition'
,
'OR NOT LIKE'
=>
'buildLikeCondition'
,
'OR NOT ILIKE'
=>
'buildLikeCondition'
,
'EXISTS'
=>
'buildExistsCondition'
,
'NOT EXISTS'
=>
'buildExistsCondition'
,
];
/**
* Builds a SQL statement for dropping an index.
...
...
tests/unit/framework/db/QueryBuilderTest.php
View file @
5c748ddb
...
...
@@ -2,6 +2,7 @@
namespace
yiiunit\framework\db
;
use
yii\db\Query
;
use
yii\db\QueryBuilder
;
use
yii\db\Schema
;
use
yii\db\mysql\QueryBuilder
as
MysqlQueryBuilder
;
...
...
@@ -24,15 +25,15 @@ class QueryBuilderTest extends DatabaseTestCase
{
switch
(
$this
->
driverName
)
{
case
'mysql'
:
return
new
MysqlQueryBuilder
(
$this
->
getConnection
());
return
new
MysqlQueryBuilder
(
$this
->
getConnection
(
true
,
false
));
case
'sqlite'
:
return
new
SqliteQueryBuilder
(
$this
->
getConnection
());
return
new
SqliteQueryBuilder
(
$this
->
getConnection
(
true
,
false
));
case
'mssql'
:
return
new
MssqlQueryBuilder
(
$this
->
getConnection
());
return
new
MssqlQueryBuilder
(
$this
->
getConnection
(
true
,
false
));
case
'pgsql'
:
return
new
PgsqlQueryBuilder
(
$this
->
getConnection
());
return
new
PgsqlQueryBuilder
(
$this
->
getConnection
(
true
,
false
));
case
'cubrid'
:
return
new
CubridQueryBuilder
(
$this
->
getConnection
());
return
new
CubridQueryBuilder
(
$this
->
getConnection
(
true
,
false
));
}
throw
new
\Exception
(
'Test is not implemented for '
.
$this
->
driverName
);
}
...
...
@@ -113,6 +114,58 @@ class QueryBuilderTest extends DatabaseTestCase
}
}
public
function
conditionProvider
()
{
$conditions
=
[
// empty values
[
[
'like'
,
'name'
,
[]],
'0=1'
,
[]
],
[
[
'not like'
,
'name'
,
[]],
''
,
[]
],
[
[
'or like'
,
'name'
,
[]],
'0=1'
,
[]
],
[
[
'or not like'
,
'name'
,
[]],
''
,
[]
],
// simple like
[
[
'like'
,
'name'
,
'heyho'
],
'"name" LIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
[
[
'not like'
,
'name'
,
'heyho'
],
'"name" NOT LIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
[
[
'or like'
,
'name'
,
'heyho'
],
'"name" LIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
[
[
'or not like'
,
'name'
,
'heyho'
],
'"name" NOT LIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
// like for many values
[
[
'like'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" LIKE :qp0 AND "name" LIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
[
[
'not like'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" NOT LIKE :qp0 AND "name" NOT LIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
[
[
'or like'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" LIKE :qp0 OR "name" LIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
[
[
'or not like'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" NOT LIKE :qp0 OR "name" NOT LIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
// TODO add more conditions
// IN
// NOT
// ...
];
// adjust dbms specific escaping
foreach
(
$conditions
as
$i
=>
$condition
)
{
switch
(
$this
->
driverName
)
{
case
'mssql'
:
case
'mysql'
:
case
'sqlite'
:
$conditions
[
$i
][
1
]
=
str_replace
(
'"'
,
'`'
,
$condition
[
1
]);
break
;
}
}
return
$conditions
;
}
/**
* @dataProvider conditionProvider
*/
public
function
testBuildCondition
(
$condition
,
$expected
,
$expectedParams
)
{
$query
=
(
new
Query
())
->
where
(
$condition
);
list
(
$sql
,
$params
)
=
$this
->
getQueryBuilder
()
->
build
(
$query
);
$this
->
assertEquals
(
$expectedParams
,
$params
);
$this
->
assertEquals
(
'SELECT *'
.
(
empty
(
$expected
)
?
''
:
' WHERE '
.
$expected
),
$sql
);
}
public
function
testAddDropPrimaryKey
()
{
$tableName
=
'constraints'
;
...
...
tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php
View file @
5c748ddb
...
...
@@ -74,4 +74,30 @@ class PostgreSQLQueryBuilderTest extends QueryBuilderTest
[
Schema
::
TYPE_MONEY
.
' NOT NULL'
,
'numeric(19,4) NOT NULL'
],
];
}
public
function
conditionProvider
()
{
return
array_merge
(
parent
::
conditionProvider
(),
[
// adding conditions for ILIKE i.e. case insensitive LIKE
// http://www.postgresql.org/docs/8.3/static/functions-matching.html#FUNCTIONS-LIKE
// empty values
[
[
'ilike'
,
'name'
,
[]],
'0=1'
,
[]
],
[
[
'not ilike'
,
'name'
,
[]],
''
,
[]
],
[
[
'or ilike'
,
'name'
,
[]],
'0=1'
,
[]
],
[
[
'or not ilike'
,
'name'
,
[]],
''
,
[]
],
// simple ilike
[
[
'ilike'
,
'name'
,
'heyho'
],
'"name" ILIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
[
[
'not ilike'
,
'name'
,
'heyho'
],
'"name" NOT ILIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
[
[
'or ilike'
,
'name'
,
'heyho'
],
'"name" ILIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
[
[
'or not ilike'
,
'name'
,
'heyho'
],
'"name" NOT ILIKE :qp0'
,
[
':qp0'
=>
'%heyho%'
]
],
// ilike for many values
[
[
'ilike'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" ILIKE :qp0 AND "name" ILIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
[
[
'not ilike'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" NOT ILIKE :qp0 AND "name" NOT ILIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
[
[
'or ilike'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" ILIKE :qp0 OR "name" ILIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
[
[
'or not ilike'
,
'name'
,
[
'heyho'
,
'abc'
]],
'"name" NOT ILIKE :qp0 OR "name" NOT ILIKE :qp1'
,
[
':qp0'
=>
'%heyho%'
,
':qp1'
=>
'%abc%'
]
],
]);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment