From d6d6f65c8c71fe4454bd11f5842b689e24a14187 Mon Sep 17 00:00:00 2001 From: Joey Smith Date: Sun, 23 Nov 2025 22:50:00 -0600 Subject: [PATCH] Attempt to improve adapter specific platform testing. Signed-off-by: Joey Smith Signed-off-by: Joey Smith --- composer.json | 4 + test/unit/DeprecatedAssertionsTrait.php | 36 + test/unit/Sql/Ddl/AlterTableTest.php | 118 ++ test/unit/Sql/Ddl/CreateTableTest.php | 154 ++ test/unit/Sql/Ddl/DropTableTest.php | 24 + test/unit/Sql/ExpressionTest.php | 183 +++ test/unit/Sql/JoinTest.php | 145 ++ test/unit/Sql/Platform/PlatformTest.php | 68 + test/unit/Sql/SelectTest.php | 1453 +++++++++++++++++ test/unit/TestAsset/DeleteIgnore.php | 27 + test/unit/TestAsset/TrustingMysqlPlatform.php | 21 + 11 files changed, 2233 insertions(+) create mode 100644 test/unit/DeprecatedAssertionsTrait.php create mode 100644 test/unit/Sql/Ddl/AlterTableTest.php create mode 100644 test/unit/Sql/Ddl/CreateTableTest.php create mode 100644 test/unit/Sql/Ddl/DropTableTest.php create mode 100644 test/unit/Sql/ExpressionTest.php create mode 100644 test/unit/Sql/JoinTest.php create mode 100644 test/unit/Sql/Platform/PlatformTest.php create mode 100644 test/unit/Sql/SelectTest.php create mode 100644 test/unit/TestAsset/DeleteIgnore.php create mode 100644 test/unit/TestAsset/TrustingMysqlPlatform.php diff --git a/composer.json b/composer.json index e2f0bcf..5ad4aff 100644 --- a/composer.json +++ b/composer.json @@ -66,6 +66,10 @@ "@test", "@test-integration" ], + "unit-test": [ + "@test", + "@test-integration" + ], "cs-check": "phpcs", "cs-fix": "phpcbf", "test": "phpunit --colors=always --testsuite \"unit test\"", diff --git a/test/unit/DeprecatedAssertionsTrait.php b/test/unit/DeprecatedAssertionsTrait.php new file mode 100644 index 0000000..005493a --- /dev/null +++ b/test/unit/DeprecatedAssertionsTrait.php @@ -0,0 +1,36 @@ +setAccessible(true); + Assert::assertEquals($expected, $r->getValue($instance), $message); + } + + /** + * @throws ReflectionException + */ + public function readAttribute(object $instance, string $attribute): mixed + { + $r = new ReflectionProperty($instance, $attribute); + /** @psalm-suppress UnusedMethodCall */ + $r->setAccessible(true); + return $r->getValue($instance); + } +} diff --git a/test/unit/Sql/Ddl/AlterTableTest.php b/test/unit/Sql/Ddl/AlterTableTest.php new file mode 100644 index 0000000..ecb4e14 --- /dev/null +++ b/test/unit/Sql/Ddl/AlterTableTest.php @@ -0,0 +1,118 @@ +getRawState('table')); + self::assertSame($at, $at->setTable('test')); + self::assertEquals('test', $at->getRawState('table')); + } + + public function testAddColumn(): void + { + $at = new AlterTable(); + /** @var ColumnInterface $colMock */ + $colMock = $this->getMockBuilder(ColumnInterface::class)->getMock(); + self::assertSame($at, $at->addColumn($colMock)); + self::assertEquals([$colMock], $at->getRawState($at::ADD_COLUMNS)); + } + + public function testChangeColumn(): void + { + $at = new AlterTable(); + /** @var ColumnInterface $colMock */ + $colMock = $this->getMockBuilder(ColumnInterface::class)->getMock(); + self::assertSame($at, $at->changeColumn('newname', $colMock)); + self::assertEquals(['newname' => $colMock], $at->getRawState($at::CHANGE_COLUMNS)); + } + + public function testDropColumn(): void + { + $at = new AlterTable(); + self::assertSame($at, $at->dropColumn('foo')); + self::assertEquals(['foo'], $at->getRawState($at::DROP_COLUMNS)); + } + + public function testDropConstraint(): void + { + $at = new AlterTable(); + self::assertSame($at, $at->dropConstraint('foo')); + self::assertEquals(['foo'], $at->getRawState($at::DROP_CONSTRAINTS)); + } + + public function testAddConstraint(): void + { + $at = new AlterTable(); + /** @var ConstraintInterface $conMock */ + $conMock = $this->getMockBuilder(ConstraintInterface::class)->getMock(); + self::assertSame($at, $at->addConstraint($conMock)); + self::assertEquals([$conMock], $at->getRawState($at::ADD_CONSTRAINTS)); + } + + public function testDropIndex(): void + { + $at = new AlterTable(); + self::assertSame($at, $at->dropIndex('foo')); + self::assertEquals(['foo'], $at->getRawState($at::DROP_INDEXES)); + } + + /** + * @todo Implement testGetSqlString(). + */ + public function testGetSqlString(): void + { + $at = new AlterTable('foo'); + $at->addColumn(new Column\Varchar('another', 255)); + $at->changeColumn('name', new Column\Varchar('new_name', 50)); + $at->dropColumn('foo'); + $at->addConstraint(new Constraint\ForeignKey('my_fk', 'other_id', 'other_table', 'id', 'CASCADE', 'CASCADE')); + $at->dropConstraint('my_constraint'); + $at->dropIndex('my_index'); + $expected = <<getSqlString(); + self::assertEquals( + str_replace(["\r", "\n"], "", $expected), + str_replace(["\r", "\n"], "", $actual) + ); + + $at = new AlterTable(new TableIdentifier('foo')); + $at->addColumn(new Column\Column('bar')); + $this->assertEquals("ALTER TABLE \"foo\"\n ADD COLUMN \"bar\" INTEGER NOT NULL", $at->getSqlString()); + + $at = new AlterTable(new TableIdentifier('bar', 'foo')); + $at->addColumn(new Column\Column('baz')); + $this->assertEquals("ALTER TABLE \"foo\".\"bar\"\n ADD COLUMN \"baz\" INTEGER NOT NULL", $at->getSqlString()); + } +} diff --git a/test/unit/Sql/Ddl/CreateTableTest.php b/test/unit/Sql/Ddl/CreateTableTest.php new file mode 100644 index 0000000..bbf6bd2 --- /dev/null +++ b/test/unit/Sql/Ddl/CreateTableTest.php @@ -0,0 +1,154 @@ +getRawState($ct::TABLE)); + self::assertTrue($ct->isTemporary()); + } + + public function testSetTemporary(): void + { + $ct = new CreateTable(); + self::assertSame($ct, $ct->setTemporary(false)); + self::assertFalse($ct->isTemporary()); + $ct->setTemporary(true); + self::assertTrue($ct->isTemporary()); + $ct->setTemporary('yes'); + self::assertTrue($ct->isTemporary()); + + self::assertStringStartsWith("CREATE TEMPORARY TABLE", $ct->getSqlString()); + } + + public function testIsTemporary(): void + { + $ct = new CreateTable(); + self::assertFalse($ct->isTemporary()); + $ct->setTemporary(true); + self::assertTrue($ct->isTemporary()); + } + + public function testSetTable(): CreateTable + { + $ct = new CreateTable(); + self::assertEquals('', $ct->getRawState('table')); + $ct->setTable('test'); + return $ct; + } + + #[Depends('testSetTable')] + public function testRawStateViaTable(CreateTable $ct): void + { + self::assertEquals('test', $ct->getRawState('table')); + } + + public function testAddColumn(): CreateTable + { + $column = $this->getMockBuilder(ColumnInterface::class)->getMock(); + $ct = new CreateTable(); + self::assertSame($ct, $ct->addColumn($column)); + return $ct; + } + + #[Depends('testAddColumn')] + public function testRawStateViaColumn(CreateTable $ct): void + { + $state = $ct->getRawState('columns'); + self::assertIsArray($state); + $column = array_pop($state); + self::assertInstanceOf(ColumnInterface::class, $column); + } + + public function testAddConstraint(): CreateTable + { + $constraint = $this->getMockBuilder(ConstraintInterface::class)->getMock(); + $ct = new CreateTable(); + self::assertSame($ct, $ct->addConstraint($constraint)); + return $ct; + } + + #[Depends('testAddConstraint')] + public function testRawStateViaConstraint(CreateTable $ct): void + { + $state = $ct->getRawState('constraints'); + self::assertIsArray($state); + $constraint = array_pop($state); + self::assertInstanceOf(ConstraintInterface::class, $constraint); + } + + public function testGetSqlString(): void + { + $ct = new CreateTable('foo'); + self::assertEquals("CREATE TABLE \"foo\" ( \n)", $ct->getSqlString()); + + $ct = new CreateTable('foo', true); + self::assertEquals("CREATE TEMPORARY TABLE \"foo\" ( \n)", $ct->getSqlString()); + + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + self::assertEquals("CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); + + $ct = new CreateTable('foo', true); + $ct->addColumn(new Column('bar')); + self::assertEquals("CREATE TEMPORARY TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); + + $ct = new CreateTable('foo', true); + $ct->addColumn(new Column('bar')); + $ct->addColumn(new Column('baz')); + self::assertEquals( + "CREATE TEMPORARY TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL,\n \"baz\" INTEGER NOT NULL \n)", + $ct->getSqlString() + ); + + $ct = new CreateTable('foo'); + $ct->addColumn(new Column('bar')); + $ct->addConstraint(new Constraint\PrimaryKey('bat')); + self::assertEquals( + "CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL , \n PRIMARY KEY (\"bat\") \n)", + $ct->getSqlString() + ); + + $ct = new CreateTable('foo'); + $ct->addConstraint(new Constraint\PrimaryKey('bar')); + $ct->addConstraint(new Constraint\PrimaryKey('bat')); + self::assertEquals( + "CREATE TABLE \"foo\" ( \n PRIMARY KEY (\"bar\"),\n PRIMARY KEY (\"bat\") \n)", + $ct->getSqlString() + ); + + $ct = new CreateTable(new TableIdentifier('foo')); + $ct->addColumn(new Column('bar')); + self::assertEquals("CREATE TABLE \"foo\" ( \n \"bar\" INTEGER NOT NULL \n)", $ct->getSqlString()); + + $ct = new CreateTable(new TableIdentifier('bar', 'foo')); + $ct->addColumn(new Column('baz')); + self::assertEquals("CREATE TABLE \"foo\".\"bar\" ( \n \"baz\" INTEGER NOT NULL \n)", $ct->getSqlString()); + } +} diff --git a/test/unit/Sql/Ddl/DropTableTest.php b/test/unit/Sql/Ddl/DropTableTest.php new file mode 100644 index 0000000..e4a2780 --- /dev/null +++ b/test/unit/Sql/Ddl/DropTableTest.php @@ -0,0 +1,24 @@ +getSqlString()); + + $dt = new DropTable(new TableIdentifier('foo')); + self::assertEquals('DROP TABLE "foo"', $dt->getSqlString()); + + $dt = new DropTable(new TableIdentifier('bar', 'foo')); + self::assertEquals('DROP TABLE "foo"."bar"', $dt->getSqlString()); + } +} diff --git a/test/unit/Sql/ExpressionTest.php b/test/unit/Sql/ExpressionTest.php new file mode 100644 index 0000000..2852bab --- /dev/null +++ b/test/unit/Sql/ExpressionTest.php @@ -0,0 +1,183 @@ +setExpression('Foo Bar'); + self::assertSame($expression, $return); + return $return; + } + + public function testSetExpressionException(): void + { + $expression = new Expression(); + $this->expectException(TypeError::class); + /** @psalm-suppress NullArgument - ensure an exception is thrown */ + $expression->setExpression(null); + + $expression = new Expression(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Supplied expression must not be an empty string.'); + $expression->setExpression(''); + } + + #[Depends('testSetExpression')] + public function testGetExpression(Expression $expression): void + { + self::assertEquals('Foo Bar', $expression->getExpression()); + } + + public function testSetParameters(): Expression + { + $expression = new Expression(); + $return = $expression->setParameters('foo'); + self::assertSame($expression, $return); + return $return; + } + + public function testSetParametersException(): void + { + $expression = new Expression('', 'foo'); + + $this->expectException(TypeError::class); + /** @psalm-suppress NullArgument - ensure an exception is thrown */ + $expression->setParameters(null); + } + + #[Depends('testSetParameters')] + public function testGetParameters(Expression $expression): void + { + self::assertEquals('foo', $expression->getParameters()); + } + + public function testGetExpressionData(): void + { + $expression = new Expression( + 'X SAME AS ? AND Y = ? BUT LITERALLY ?', + [ + ['foo' => Expression::TYPE_IDENTIFIER], + [5 => Expression::TYPE_VALUE], + ['FUNC(FF%X)' => Expression::TYPE_LITERAL], + ] + ); + + $expected = [ + [ + 'X SAME AS %s AND Y = %s BUT LITERALLY %s', + ['foo', 5, 'FUNC(FF%X)'], + [Expression::TYPE_IDENTIFIER, Expression::TYPE_VALUE, Expression::TYPE_LITERAL], + ], + ]; + + self::assertEquals($expected, $expression->getExpressionData()); + } + + public function testGetExpressionDataWillEscapePercent(): void + { + $expression = new Expression('X LIKE "foo%"'); + self::assertEquals( + ['X LIKE "foo%%"'], + $expression->getExpressionData() + ); + } + + public function testConstructorWithLiteralZero(): void + { + $expression = new Expression('0'); + self::assertSame('0', $expression->getExpression()); + } + + #[Group('7407')] + public function testGetExpressionPreservesPercentageSignInFromUnixtime(): void + { + $expressionString = 'FROM_UNIXTIME(date, "%Y-%m")'; + $expression = new Expression($expressionString); + + self::assertSame($expressionString, $expression->getExpression()); + } + + public function testNumberOfReplacementsConsidersWhenSameVariableIsUsedManyTimes(): void + { + $expression = new Expression('uf.user_id = :user_id OR uf.friend_id = :user_id', ['user_id' => 1]); + + self::assertSame( + [ + [ + 'uf.user_id = :user_id OR uf.friend_id = :user_id', + [1], + ['value'], + ], + ], + $expression->getExpressionData() + ); + } + + #[DataProvider('falsyExpressionParametersProvider')] + public function testConstructorWithFalsyValidParameters(mixed $falsyParameter): void + { + $expression = new Expression('?', $falsyParameter); + self::assertSame($falsyParameter, $expression->getParameters()); + } + + public function testConstructorWithInvalidParameter(): void + { + $this->expectException(TypeError::class); + new Expression('?', (object) []); + } + + /** @psalm-return array */ + public static function falsyExpressionParametersProvider(): array + { + return [ + [''], + ['0'], + [0], + [0.0], + [false], + [[]], + ]; + } + + public function testNumberOfReplacementsForExpressionWithParameters(): void + { + $expression = new Expression(':a + :b', ['a' => 1, 'b' => 2]); + + self::assertSame( + [ + [ + ':a + :b', + [1, 2], + ['value', 'value'], + ], + ], + $expression->getExpressionData() + ); + } +} diff --git a/test/unit/Sql/JoinTest.php b/test/unit/Sql/JoinTest.php new file mode 100644 index 0000000..b030763 --- /dev/null +++ b/test/unit/Sql/JoinTest.php @@ -0,0 +1,145 @@ +next(); + + self::assertAttributeEquals(1, 'position', $join); + } + + /** + * @throws ReflectionException + */ + public function testRewindResetsPositionToZero(): void + { + $join = new Join(); + + $join->next(); + $join->next(); + self::assertAttributeEquals(2, 'position', $join); + + $join->rewind(); + self::assertAttributeEquals(0, 'position', $join); + } + + public function testKeyReturnsTheCurrentPosition(): void + { + $join = new Join(); + + $join->next(); + $join->next(); + $join->next(); + + self::assertEquals(3, $join->key()); + } + + public function testCurrentReturnsTheCurrentJoinSpecification(): void + { + $name = 'baz'; + $on = 'foo.id = baz.id'; + + $join = new Join(); + $join->join($name, $on); + + $expectedSpecification = [ + 'name' => $name, + 'on' => $on, + 'columns' => [Select::SQL_STAR], + 'type' => Join::JOIN_INNER, + ]; + + self::assertEquals($expectedSpecification, $join->current()); + } + + public function testValidReturnsTrueIfTheIteratorIsAtAValidPositionAndFalseIfNot(): void + { + $join = new Join(); + $join->join('baz', 'foo.id = baz.id'); + + self::assertTrue($join->valid()); + + $join->next(); + + self::assertFalse($join->valid()); + } + + #[TestDox('unit test: Test join() returns Join object (is chainable)')] + public function testJoin(): void + { + $join = new Join(); + $return = $join->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); + self::assertSame($join, $return); + } + + public function testJoinFullOuter(): void + { + $join = new Join(); + $return = $join->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_FULL_OUTER); + self::assertSame($join, $return); + } + + public function testJoinWillThrowAnExceptionIfNameIsNoValid(): void + { + $join = new Join(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("join() expects '' as a single element associative array"); + /** @psalm-suppress InvalidArgument */ + $join->join([], false); + } + + #[TestDox('unit test: Test count() returns correct count')] + public function testCount(): void + { + $join = new Join(); + $join->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); + $join->join('bar', 'foo.fooId = bar.fooId', Join::JOIN_LEFT); + + self::assertEquals(2, $join->count()); + self::assertCount($join->count(), $join->getJoins()); + } + + #[TestDox('unit test: Test reset() resets the joins')] + public function testReset(): void + { + $join = new Join(); + $join->join('baz', 'foo.fooId = baz.fooId', Join::JOIN_LEFT); + $join->join('bar', 'foo.fooId = bar.fooId', Join::JOIN_LEFT); + $join->reset(); + + self::assertEquals(0, $join->count()); + } +} diff --git a/test/unit/Sql/Platform/PlatformTest.php b/test/unit/Sql/Platform/PlatformTest.php new file mode 100644 index 0000000..b185538 --- /dev/null +++ b/test/unit/Sql/Platform/PlatformTest.php @@ -0,0 +1,68 @@ +getAdapter(); + $sqlPlatform = new SqlPlatform(); + $decorators = $sqlPlatform->getDecorators(); + self::assertIsArray($decorators); + } + + public function testGetTypeDecoratorReturnsDecoratorForSelect(): void + { + $adapter = $this->getAdapter(); + $sqlPlatform = new SqlPlatform(); + $select = new Select(); + $decorator = $sqlPlatform->getTypeDecorator($select); + self::assertInstanceOf(SelectDecorator::class, $decorator); + } + + protected function getAdapter(): Adapter + { + /** @var DriverInterface|MockObject $mockDriver */ + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + + $adapterPlatform = new AdapterPlatform($mockDriver); + + $mockDriver->expects($this->any()) + ->method('formatParameterName') + ->willReturn('?'); + $mockDriver->expects($this->any()) + ->method('createStatement') + ->willReturnCallback(fn() => new StatementContainer()); + + return new Adapter( + $mockDriver, + $adapterPlatform, + new ResultSet() + ); + } +} diff --git a/test/unit/Sql/SelectTest.php b/test/unit/Sql/SelectTest.php new file mode 100644 index 0000000..ea8525c --- /dev/null +++ b/test/unit/Sql/SelectTest.php @@ -0,0 +1,1453 @@ +getRawState('table')); + } + + #[TestDox('unit test: Test from() returns Select object (is chainable)')] + public function testFrom(): Select + { + $select = new Select(); + $return = $select->from('foo'); + self::assertSame($select, $return); + + return $return; + } + + #[Depends('testFrom')] + #[TestDox('unit test: Test getRawState() returns information populated via from()')] + public function testGetRawStateViaFrom(Select $select): void + { + self::assertEquals('foo', $select->getRawState('table')); + } + + #[TestDox('unit test: Test quantifier() returns Select object (is chainable)')] + public function testQuantifier(): Select + { + $select = new Select(); + $return = $select->quantifier($select::QUANTIFIER_DISTINCT); + self::assertSame($select, $return); + return $return; + } + + #[Depends('testQuantifier')] + #[TestDox('unit test: Test getRawState() returns information populated via quantifier()')] + public function testGetRawStateViaQuantifier(Select $select): void + { + self::assertEquals(Select::QUANTIFIER_DISTINCT, $select->getRawState('quantifier')); + } + + #[TestDox('unit test: Test quantifier() accepts expression')] + public function testQuantifierParameterExpressionInterface(): void + { + $expr = $this->getMockBuilder(ExpressionInterface::class)->onlyMethods([])->getMock(); + $select = new Select(); + /** @psalm-suppress InvalidArgument */ + $select->quantifier($expr); + self::assertSame( + $expr, + $select->getRawState(Select::QUANTIFIER) + ); + } + + #[TestDox('unit test: Test columns() returns Select object (is chainable)')] + public function testColumns(): Select + { + $select = new Select(); + $return = $select->columns(['foo', 'bar']); + self::assertSame($select, $return); + + return $select; + } + + #[TestDox('unit test: Test isTableReadOnly() returns correct state for read only')] + public function testIsTableReadOnly(): void + { + $select = new Select('foo'); + self::assertTrue($select->isTableReadOnly()); + + $select = new Select(); + self::assertFalse($select->isTableReadOnly()); + } + + #[Depends('testColumns')] + #[TestDox('unit test: Test getRawState() returns information populated via columns()')] + public function testGetRawStateViaColumns(Select $select): void + { + self::assertEquals(['foo', 'bar'], $select->getRawState('columns')); + } + + #[TestDox('unit test: Test join() returns same Select object (is chainable)')] + public function testJoin(): Select + { + $select = new Select(); + $return = $select->join('foo', 'x = y'); + self::assertSame($select, $return); + + return $return; + } + + #[TestDox('unit test: Test join() exception with bad join')] + public function testBadJoin(): void + { + $select = new Select(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("expects 'foo' as"); + $select->join(['foo'], 'x = y'); + } + + /** + * @throws ReflectionException + */ + #[TestDox('unit test: Test processJoins() exception with bad join name')] + public function testBadJoinName(): void + { + $mockExpression = $this->getMockBuilder(ExpressionInterface::class) + ->getMock(); + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); + $parameterContainer = new ParameterContainer(); + + $select = new Select(); + $select->join(['foo' => $mockExpression], 'x = y'); + + $sr = new ReflectionObject($select); + + $mr = $sr->getMethod('processJoins'); + /** @psalm-suppress UnusedMethodCall */ + $mr->setAccessible(true); + + $this->expectException(InvalidArgumentException::class); + $mr->invokeArgs($select, [new AdapterPlatform($mockDriver), $mockDriver, $parameterContainer]); + } + + #[Depends('testJoin')] + #[TestDox('unit test: Test getRawState() returns information populated via join()')] + public function testGetRawStateViaJoin(Select $select): void + { + $joins = $select->getRawState('joins'); + self::assertInstanceOf(Join::class, $joins); + self::assertEquals( + [ + [ + 'name' => 'foo', + 'on' => 'x = y', + 'columns' => [Select::SQL_STAR], + 'type' => Select::JOIN_INNER, + ], + ], + $joins->getJoins() + ); + } + + #[TestDox('unit test: Test where() returns Select object (is chainable)')] + public function testWhereReturnsSameSelectObject(): void + { + $select = new Select(); + self::assertSame($select, $select->where('x = y')); + } + + #[TestDox('unit test: Test where() will accept a string for the predicate to create an expression predicate')] + public function testWhereArgument1IsString(): void + { + $select = new Select(); + $select->where('x = ?'); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Predicate\Expression::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('x = ?', $predicates[0][1]->getExpression()); + + $select = new Select(); + $select->where('x = y'); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); + } + + #[TestDox('unit test: Test where() will accept an array with a string key (containing ?) used as an + expression with placeholder')] + public function testWhereArgument1IsAssociativeArrayContainingReplacementCharacter(): void + { + $select = new Select(); + $select->where(['foo > ?' => 5]); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Predicate\Expression::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('foo > ?', $predicates[0][1]->getExpression()); + self::assertEquals([5], $predicates[0][1]->getParameters()); + } + + #[TestDox('unit test: Test where() will accept any array with string key (without ?) to be used + as Operator predicate')] + public function testWhereArgument1IsAssociativeArrayNotContainingReplacementCharacter(): void + { + $select = new Select(); + $select->where(['name' => 'Ralph', 'age' => 33]); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(2, $predicates); + self::assertIsArray($predicates[0]); + self::assertIsArray($predicates[1]); + + self::assertInstanceOf(Operator::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('name', $predicates[0][1]->getLeft()); + self::assertEquals('Ralph', $predicates[0][1]->getRight()); + + self::assertInstanceOf(Operator::class, $predicates[1][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[1][0]); + self::assertEquals('age', $predicates[1][1]->getLeft()); + self::assertEquals(33, $predicates[1][1]->getRight()); + + $select = new Select(); + $select->where(['x = y']); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); + } + + #[TestDox(' + unit test: Test where() will accept any array with string key (without ?) with Predicate throw Exception + ')] + public function testWhereArgument1IsAssociativeArrayIsPredicate(): void + { + $select = new Select(); + $where = [ + 'name' => new Predicate\Literal(`name = 'Ralph'`), + 'age' => new Predicate\Expression('age = ?', 33), + ]; + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Using Predicate must not use string keys'); + $select->where($where); + } + + #[TestDox('unit test: Test where() will accept an indexed array to be used by joining string expressions')] + public function testWhereArgument1IsIndexedArray(): void + { + $select = new Select(); + $select->where(['name = `Ralph`']); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_AND, $predicates[0][0]); + self::assertEquals('name = `Ralph`', $predicates[0][1]->getLiteral()); + } + + #[TestDox('unit test: Test where() will accept an indexed array to be used by joining string expressions, + combined by OR')] + public function testWhereArgument1IsIndexedArrayArgument2IsOr(): void + { + $select = new Select(); + $select->where(['name = `Ralph`'], Predicate\PredicateSet::OP_OR); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertCount(1, $predicates); + self::assertIsArray($predicates[0]); + self::assertInstanceOf(Literal::class, $predicates[0][1]); + self::assertEquals(Predicate\PredicateSet::OP_OR, $predicates[0][0]); + self::assertEquals('name = `Ralph`', $predicates[0][1]->getLiteral()); + } + + #[TestDox('unit test: Test where() will accept a closure to be executed with Where object as argument')] + public function testWhereArgument1IsClosure(): void + { + $select = new Select(); + /** @var Where $where */ + $where = $select->getRawState('where'); + + $select->where(function (Where $what) use ($where): void { + self::assertSame($where, $what); + }); + } + + #[TestDox('unit test: Test where() will accept any Predicate object as-is')] + public function testWhereArgument1IsPredicate(): void + { + $select = new Select(); + $predicate = new Predicate\Predicate([ + new Predicate\Expression('name = ?', 'Ralph'), + new Predicate\Expression('age = ?', 33), + ]); + $select->where($predicate); + + /** @var Where $where */ + $where = $select->getRawState('where'); + $predicates = $where->getPredicates(); + self::assertIsArray($predicates[0]); + self::assertSame($predicate, $predicates[0][1]); + } + + #[TestDox('unit test: Test where() will accept a Where object')] + public function testWhereArgument1IsWhereObject(): void + { + $select = new Select(); + $select->where($newWhere = new Where()); + self::assertSame($newWhere, $select->getRawState('where')); + } + + /** + * @throws ReflectionException + */ + #[TestDox('unit test: Test order()')] + public function testOrder(): void + { + $select = new Select(); + $return = $select->order('id DESC'); + self::assertSame($select, $return); // test fluent interface + self::assertEquals(['id DESC'], $select->getRawState('order')); + + $select = new Select(); + $select->order('id DESC') + ->order('name ASC, age DESC'); + self::assertEquals(['id DESC', 'name ASC', 'age DESC'], $select->getRawState('order')); + + $select = new Select(); + $select->order(['name ASC', 'age DESC']); + self::assertEquals(['name ASC', 'age DESC'], $select->getRawState('order')); + + $select = new Select(); + $select->order(new Expression('RAND()')); + $sr = new ReflectionObject($select); + $method = $sr->getMethod('processOrder'); + /** @psalm-suppress UnusedMethodCall */ + $method->setAccessible(true); + self::assertEquals( + [[['RAND()']]], + $method->invokeArgs($select, [new TrustingMysqlPlatform()]) + ); + + $select = new Select(); + /** @psalm-suppress InvalidArgument - mocked Operator */ + $select->order( + $this->getMockBuilder(Operator::class) + ->onlyMethods([]) + ->setConstructorArgs(['rating', '<', '10']) + ->getMock() + ); + $sr = new ReflectionObject($select); + $method = $sr->getMethod('processOrder'); + /** @psalm-suppress UnusedMethodCall */ + $method->setAccessible(true); + self::assertEquals( + [[['"rating" < \'10\'']]], + $method->invokeArgs($select, [new TrustingMysqlPlatform()]) + ); + } + + #[TestDox('unit test: Test order() correctly splits parameters.')] + public function testOrderCorrectlySplitsParameter(): void + { + $select = new Select(); + $select->order('name desc'); + self::assertEquals( + 'SELECT * ORDER BY "name" DESC', + $select->getSqlString(new TrustingMysqlPlatform()) + ); + } + + #[TestDox(': unit test: test limit()')] + public function testLimit(): Select + { + $select = new Select(); + self::assertSame($select, $select->limit(5)); + return $select; + } + + #[Depends('testLimit')] + #[TestDox(': unit test: Test getRawState() returns information populated via limit()')] + public function testGetRawStateViaLimit(Select $select): void + { + $limit = $select->getRawState((string) $select::LIMIT); + self::assertIsNumeric($limit); + self::assertEquals(5, $limit); + } + + #[TestDox(': unit test: test limit() throws exception when invalid parameter passed')] + public function testLimitExceptionOnInvalidParameter(): void + { + $select = new Select(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('PhpDb\Sql\Select::limit expects parameter to be numeric'); + $select->limit('foobar'); + } + + #[TestDox(': unit test: test offset()')] + public function testOffset(): Select + { + $select = new Select(); + self::assertSame($select, $select->offset(10)); + return $select; + } + + #[Depends('testOffset')] + #[TestDox(': unit test: Test getRawState() returns information populated via offset()')] + public function testGetRawStateViaOffset(Select $select): void + { + $offset = $select->getRawState((string) $select::OFFSET); + self::assertIsNumeric($offset); + self::assertEquals(10, $offset); + } + + #[TestDox(': unit test: test offset() throws exception when invalid parameter passed')] + public function testOffsetExceptionOnInvalidParameter(): void + { + $select = new Select(); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('PhpDb\Sql\Select::offset expects parameter to be numeric'); + $select->offset('foobar'); + } + + #[TestDox('unit test: Test group() returns same Select object (is chainable)')] + public function testGroup(): Select + { + $select = new Select(); + $return = $select->group(['col1', 'col2']); + self::assertSame($select, $return); + + return $return; + } + + #[Depends('testGroup')] + #[TestDox('unit test: Test getRawState() returns information populated via group()')] + public function testGetRawStateViaGroup(Select $select): void + { + self::assertEquals( + ['col1', 'col2'], + $select->getRawState('group') + ); + } + + #[TestDox('unit test: Test having() returns same Select object (is chainable)')] + public function testHaving(): Select + { + $select = new Select(); + $return = $select->having(['x = ?' => 5]); + self::assertSame($select, $return); + + return $return; + } + + #[TestDox('unit test: Test having() returns same Select object (is chainable)')] + public function testHavingArgument1IsHavingObject(): Select + { + $select = new Select(); + $having = new Having(); + $return = $select->having($having); + self::assertSame($select, $return); + self::assertSame($having, $select->getRawState('having')); + + return $return; + } + + #[Depends('testHaving')] + #[TestDox('unit test: Test getRawState() returns information populated via having()')] + public function testGetRawStateViaHaving(Select $select): void + { + self::assertInstanceOf(Having::class, $select->getRawState('having')); + } + + #[TestDox('unit test: Test combine() returns same Select object (is chainable)')] + public function testCombine(): Select + { + $select = new Select(); + $combine = new Select(); + $return = $select->combine($combine, $select::COMBINE_UNION, 'ALL'); + self::assertSame($select, $return); + + return $return; + } + + #[Depends('testCombine')] + #[TestDox('unit test: Test getRawState() returns information populated via combine()')] + public function testGetRawStateViaCombine(Select $select): void + { + /** @var array $state */ + $state = $select->getRawState('combine'); + self::assertInstanceOf(Select::class, $state['select']); + self::assertNotSame($select, $state['select']); + self::assertEquals(Select::COMBINE_UNION, $state['type']); + self::assertEquals('ALL', $state['modifier']); + } + + #[TestDox('unit test: Test reset() resets internal stat of Select object, based on input')] + public function testReset(): void + { + $select = new Select(); + + // table + $select->from('foo'); + self::assertEquals('foo', $select->getRawState(Select::TABLE)); + $select->reset(Select::TABLE); + self::assertNull($select->getRawState(Select::TABLE)); + + // columns + $select->columns(['foo']); + self::assertEquals(['foo'], $select->getRawState(Select::COLUMNS)); + $select->reset(Select::COLUMNS); + self::assertEmpty($select->getRawState(Select::COLUMNS)); + + // joins + $select->join('foo', 'id = boo'); + $joins = $select->getRawState(Select::JOINS); + self::assertInstanceOf(Join::class, $joins); + self::assertEquals( + [['name' => 'foo', 'on' => 'id = boo', 'columns' => ['*'], 'type' => 'inner']], + $joins->getJoins() + ); + $select->reset(Select::JOINS); + $emptyJoins = $select->getRawState(Select::JOINS); + self::assertInstanceOf(Join::class, $emptyJoins); + self::assertEmpty($emptyJoins->getJoins()); + + // where + $select->where('foo = bar'); + /** @var Where $where1 */ + $where1 = $select->getRawState(Select::WHERE); + self::assertEquals(1, $where1->count()); + $select->reset(Select::WHERE); + /** @var Where $where2 */ + $where2 = $select->getRawState(Select::WHERE); + self::assertEquals(0, $where2->count()); + self::assertNotSame($where1, $where2); + + // group + $select->group(['foo']); + self::assertEquals(['foo'], $select->getRawState(Select::GROUP)); + $select->reset(Select::GROUP); + self::assertEmpty($select->getRawState(Select::GROUP)); + + // having + $select->having('foo = bar'); + /** @var Having $having1 */ + $having1 = $select->getRawState(Select::HAVING); + self::assertEquals(1, $having1->count()); + $select->reset(Select::HAVING); + /** @var Having $having2 */ + $having2 = $select->getRawState(Select::HAVING); + self::assertEquals(0, $having2->count()); + self::assertNotSame($having1, $having2); + + // limit + $select->limit(5); + self::assertEquals(5, $select->getRawState(Select::LIMIT)); + $select->reset(Select::LIMIT); + self::assertNull($select->getRawState(Select::LIMIT)); + + // offset + $select->offset(10); + self::assertEquals(10, $select->getRawState(Select::OFFSET)); + $select->reset(Select::OFFSET); + self::assertNull($select->getRawState(Select::OFFSET)); + + // order + $select->order('foo asc'); + self::assertEquals(['foo asc'], $select->getRawState(Select::ORDER)); + $select->reset(Select::ORDER); + self::assertEmpty($select->getRawState(Select::ORDER)); + } + + #[DataProvider('providerData')] + #[TestDox('unit test: Test prepareStatement() will produce expected sql and parameters based on + a variety of provided arguments [uses data provider]')] + public function testPrepareStatement( + Select $select, + string $expectedSqlString, + array $expectedParameters, + mixed $unused1, + mixed $unused2, + bool $useNamedParameters = false + ): void { + + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + $mockDriver + ->expects($this->any()) + ->method('formatParameterName') + ->willReturnCallback(fn(string $name) => $useNamedParameters ? ':' . $name : '?'); + + $mockPlatform = $this->getMockBuilder(AdapterPlatform::class) + ->onlyMethods([]) + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + $mockAdapter = $this->getMockBuilder(Adapter::class) + ->onlyMethods([]) + ->setConstructorArgs([$mockDriver, $mockPlatform, new ResultSet()]) + ->getMock(); + + $parameterContainer = new ParameterContainer(); + + $mockStatement = $this->getMockBuilder(StatementInterface::class)->getMock(); + $mockStatement->expects($this->any())->method('getParameterContainer') + ->willReturn($parameterContainer); + $mockStatement->expects($this->any())->method('setSql')->with($this->equalTo($expectedSqlString)); + + $select->prepareStatement($mockAdapter, $mockStatement); + + if ($expectedParameters) { + self::assertEquals($expectedParameters, $parameterContainer->getNamedArray()); + } + } + + #[Group('Laminas-5192')] + public function testSelectUsingTableIdentifierWithEmptyScheme(): void + { + $select = new Select(); + $select->from(new TableIdentifier('foo')); + $select->join(new TableIdentifier('bar'), 'foo.id = bar.fooid'); + + self::assertEquals( + 'SELECT "foo".*, "bar".* FROM "foo" INNER JOIN "bar" ON "foo"."id" = "bar"."fooid"', + $select->getSqlString(new TrustingMysqlPlatform()) + ); + } + + #[DataProvider('providerData')] + #[TestDox('unit test: Test getSqlString() will produce expected sql and parameters based on + a variety of provided arguments [uses data provider]')] + public function testGetSqlString(Select $select, mixed $unused, mixed $unused2, string $expectedSqlString): void + { + $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + + $mockPlatform = $this->getMockBuilder(AdapterPlatform::class) + ->onlyMethods([]) + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + self::assertEquals($expectedSqlString, $select->getSqlString($mockPlatform)); + } + + #[TestDox('unit test: Test __get() returns expected objects magically')] + public function testMagicAccessor(): void + { + $select = new Select(); + self::assertInstanceOf(Where::class, $select->where); + } + + #[TestDox('unit test: Test __clone() will clone the where object so that this select can be used + in multiple contexts')] + public function testCloning(): void + { + $select = new Select(); + $select1 = clone $select; + $select1->where('id = foo'); + $select1->having('id = foo'); + + self::assertEquals(0, $select->where->count()); + self::assertEquals(1, $select1->where->count()); + + self::assertEquals(0, $select->having->count()); + self::assertEquals(1, $select1->having->count()); + } + + /** + * @throws ReflectionException + * @return void + */ + // #[DataProvider('providerData')] + // #[TestDox('unit test: Text process*() methods will return proper array when internally called, + // part of extension API')] + // public function testProcessMethods( + // Select $select, + // mixed $unused, + // mixed $unused2, + // mixed $unused3, + // array $internalTests + // ) { + // if (! $internalTests) { + // $this->expectNotToPerformAssertions(); + // return; + // } + + // $mockDriver = $this->getMockBuilder(DriverInterface::class)->getMock(); + // $mockDriver->expects($this->any())->method('formatParameterName')->willReturn('?'); + // $parameterContainer = new ParameterContainer(); + + // $sr = new ReflectionObject($select); + + // /** + // * @var string $method + // * @var array $expected + // */ + // foreach ($internalTests as $method => $expected) { + // $mr = $sr->getMethod($method); + // /** @psalm-suppress UnusedMethodCall */ + // $mr->setAccessible(true); + // /** @psalm-suppress MixedAssignment */ + // $return = $mr->invokeArgs($select, [new Sql92(), $mockDriver, $parameterContainer]); + // self::assertEquals($expected, $return); + // } + // } + + /** + * @psalm-return array, + * 3: string, + * 4: array, + * 5: bool, + * }> + */ + public static function providerData(): array + { + // phpcs:disable Generic.Files.LineLength.TooLong + // basic table + $select0 = new Select(); + $select0->from('foo'); + $sqlPrep0 = // same + $sqlStr0 = 'SELECT `foo`.* FROM `foo`'; + $internalTests0 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + ]; + + // table as TableIdentifier + $select1 = new Select(); + $select1->from(new TableIdentifier('foo', 'bar')); + $sqlPrep1 = // same + $sqlStr1 = 'SELECT `bar`.`foo`.* FROM `bar`.`foo`'; + $internalTests1 = [ + 'processSelect' => [[['`bar`.`foo`.*']], '`bar`.`foo`'], + ]; + + // table with alias + $select2 = new Select(); + $select2->from(['f' => 'foo']); + $sqlPrep2 = // same + $sqlStr2 = 'SELECT `f`.* FROM `foo` AS `f`'; + $internalTests2 = [ + 'processSelect' => [[['`f`.*']], '`foo` AS `f`'], + ]; + + // table with alias with table as TableIdentifier + $select3 = new Select(); + $select3->from(['f' => new TableIdentifier('foo')]); + $sqlPrep3 = // same + $sqlStr3 = 'SELECT `f`.* FROM `foo` AS `f`'; + $internalTests3 = [ + 'processSelect' => [[['`f`.*']], '`foo` AS `f`'], + ]; + + // columns + $select4 = new Select(); + $select4->from('foo')->columns(['bar', 'baz']); + $sqlPrep4 = // same + $sqlStr4 = 'SELECT `foo`.`bar` AS `bar`, `foo`.`baz` AS `baz` FROM `foo`'; + $internalTests4 = [ + 'processSelect' => [[['`foo`.`bar`', '`bar`'], ['`foo`.`baz`', '`baz`']], '`foo`'], + ]; + + // columns with AS associative array + $select5 = new Select(); + $select5->from('foo')->columns(['bar' => 'baz']); + $sqlPrep5 = // same + $sqlStr5 = 'SELECT `foo`.`baz` AS `bar` FROM `foo`'; + $internalTests5 = [ + 'processSelect' => [[['`foo`.`baz`', '`bar`']], '`foo`'], + ]; + + // columns with AS associative array mixed + $select6 = new Select(); + $select6->from('foo')->columns(['bar' => 'baz', 'bam']); + $sqlPrep6 = // same + $sqlStr6 = 'SELECT `foo`.`baz` AS `bar`, `foo`.`bam` AS `bam` FROM `foo`'; + $internalTests6 = [ + 'processSelect' => [[['`foo`.`baz`', '`bar`'], ['`foo`.`bam`', '`bam`']], '`foo`'], + ]; + + // columns where value is Expression, with AS + $select7 = new Select(); + $select7->from('foo')->columns(['bar' => new Expression('COUNT(some_column)')]); + $sqlPrep7 = // same + $sqlStr7 = 'SELECT COUNT(some_column) AS `bar` FROM `foo`'; + $internalTests7 = [ + 'processSelect' => [[['COUNT(some_column)', '`bar`']], '`foo`'], + ]; + + // columns where value is Expression + $select8 = new Select(); + $select8->from('foo')->columns([new Expression('COUNT(some_column) AS bar')]); + $sqlPrep8 = // same + $sqlStr8 = 'SELECT COUNT(some_column) AS bar FROM `foo`'; + $internalTests8 = [ + 'processSelect' => [[['COUNT(some_column) AS bar']], '`foo`'], + ]; + + // columns where value is Expression with parameters + $select9 = new Select(); + $select9->from('foo')->columns( + [ + new Expression( + '(COUNT(?) + ?) AS ?', + [ + ['some_column' => ExpressionInterface::TYPE_IDENTIFIER], + [5 => ExpressionInterface::TYPE_VALUE], + ['bar' => ExpressionInterface::TYPE_IDENTIFIER], + ], + ), + ] + ); + $sqlPrep9 = 'SELECT (COUNT(`some_column`) + ?) AS `bar` FROM `foo`'; + $sqlStr9 = 'SELECT (COUNT(`some_column`) + \'5\') AS `bar` FROM `foo`'; + $params9 = ['column1' => 5]; + $internalTests9 = [ + 'processSelect' => [[['(COUNT(`some_column`) + ?) AS `bar`']], '`foo`'], + ]; + + // joins (plain) + $select10 = new Select(); + $select10->from('foo')->join('zac', 'm = n'); + $sqlPrep10 = // same + $sqlStr10 = 'SELECT `foo`.*, `zac`.* FROM `foo` INNER JOIN `zac` ON `m` = `n`'; + $internalTests10 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.*']], '`foo`'], + 'processJoins' => [[['INNER', '`zac`', '`m` = `n`']]], + ]; + + // join with columns + $select11 = new Select(); + $select11->from('foo')->join('zac', 'm = n', ['bar', 'baz']); + $sqlPrep11 = // same + $sqlStr11 = 'SELECT `foo`.*, `zac`.`bar` AS `bar`, `zac`.`baz` AS `baz` FROM `foo` INNER JOIN `zac` ON `m` = `n`'; + $internalTests11 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.`bar`', '`bar`'], ['`zac`.`baz`', '`baz`']], '`foo`'], + 'processJoins' => [[['INNER', '`zac`', '`m` = `n`']]], + ]; + + // join with alternate type + $select12 = new Select(); + $select12->from('foo')->join('zac', 'm = n', ['bar', 'baz'], Select::JOIN_OUTER); + $sqlPrep12 = // same + $sqlStr12 = 'SELECT `foo`.*, `zac`.`bar` AS `bar`, `zac`.`baz` AS `baz` FROM `foo` OUTER JOIN `zac` ON `m` = `n`'; + $internalTests12 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.`bar`', '`bar`'], ['`zac`.`baz`', '`baz`']], '`foo`'], + 'processJoins' => [[['OUTER', '`zac`', '`m` = `n`']]], + ]; + + // join with column aliases + $select13 = new Select(); + $select13->from('foo')->join('zac', 'm = n', ['BAR' => 'bar', 'BAZ' => 'baz']); + $sqlPrep13 = // same + $sqlStr13 = 'SELECT `foo`.*, `zac`.`bar` AS `BAR`, `zac`.`baz` AS `BAZ` FROM `foo` INNER JOIN `zac` ON `m` = `n`'; + $internalTests13 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.`bar`', '`BAR`'], ['`zac`.`baz`', '`BAZ`']], '`foo`'], + 'processJoins' => [[['INNER', '`zac`', '`m` = `n`']]], + ]; + + // join with table aliases + $select14 = new Select(); + $select14->from('foo')->join(['b' => 'bar'], 'b.foo_id = foo.foo_id'); + $sqlPrep14 = // same + $sqlStr14 = 'SELECT `foo`.*, `b`.* FROM `foo` INNER JOIN `bar` AS `b` ON `b`.`foo_id` = `foo`.`foo_id`'; + $internalTests14 = [ + 'processSelect' => [[['`foo`.*'], ['`b`.*']], '`foo`'], + 'processJoins' => [[['INNER', '`bar` AS `b`', '`b`.`foo_id` = `foo`.`foo_id`']]], + ]; + + // where (simple string) + $select15 = new Select(); + $select15->from('foo')->where('x = 5'); + $sqlPrep15 = // same + $sqlStr15 = 'SELECT `foo`.* FROM `foo` WHERE x = 5'; + $internalTests15 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processWhere' => ['x = 5'], + ]; + + // where (returning parameters) + $select16 = new Select(); + $select16->from('foo')->where(['x = ?' => 5]); + $sqlPrep16 = 'SELECT `foo`.* FROM `foo` WHERE x = ?'; + $sqlStr16 = 'SELECT `foo`.* FROM `foo` WHERE x = \'5\''; + $params16 = ['where1' => 5]; + $internalTests16 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processWhere' => ['x = ?'], + ]; + + // group + $select17 = new Select(); + $select17->from('foo')->group(['col1', 'col2']); + $sqlPrep17 = // same + $sqlStr17 = 'SELECT `foo`.* FROM `foo` GROUP BY `col1`, `col2`'; + $internalTests17 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processGroup' => [['`col1`', '`col2`']], + ]; + + $select18 = new Select(); + $select18->from('foo')->group('col1')->group('col2'); + $sqlPrep18 = // same + $sqlStr18 = 'SELECT `foo`.* FROM `foo` GROUP BY `col1`, `col2`'; + $internalTests18 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processGroup' => [['`col1`', '`col2`']], + ]; + + $select19 = new Select(); + $select19->from('foo')->group(new Expression('DAY(?)', [['col1' => ExpressionInterface::TYPE_IDENTIFIER]])); + $sqlPrep19 = // same + $sqlStr19 = 'SELECT `foo`.* FROM `foo` GROUP BY DAY(`col1`)'; + $internalTests19 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processGroup' => [['DAY(`col1`)']], + ]; + + // having (simple string) + $select20 = new Select(); + $select20->from('foo')->having('x = 5'); + $sqlPrep20 = // same + $sqlStr20 = 'SELECT `foo`.* FROM `foo` HAVING x = 5'; + $internalTests20 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processHaving' => ['x = 5'], + ]; + + // having (returning parameters) + $select21 = new Select(); + $select21->from('foo')->having(['x = ?' => 5]); + $sqlPrep21 = 'SELECT `foo`.* FROM `foo` HAVING x = ?'; + $sqlStr21 = 'SELECT `foo`.* FROM `foo` HAVING x = \'5\''; + $params21 = ['having1' => 5]; + $internalTests21 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processHaving' => ['x = ?'], + ]; + + // order + $select22 = new Select(); + $select22->from('foo')->order('c1'); + $sqlPrep22 = + $sqlStr22 = 'SELECT `foo`.* FROM `foo` ORDER BY `c1` ASC'; + $internalTests22 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processOrder' => [[['`c1`', Select::ORDER_ASCENDING]]], + ]; + + $select23 = new Select(); + $select23->from('foo')->order(['c1', 'c2']); + $sqlPrep23 = // same + $sqlStr23 = 'SELECT `foo`.* FROM `foo` ORDER BY `c1` ASC, `c2` ASC'; + $internalTests23 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processOrder' => [[['`c1`', Select::ORDER_ASCENDING], ['`c2`', Select::ORDER_ASCENDING]]], + ]; + + $select24 = new Select(); + $select24->from('foo')->order(['c1' => 'DESC', 'c2' => 'Asc']); // notice partially lower case ASC + $sqlPrep24 = // same + $sqlStr24 = 'SELECT `foo`.* FROM `foo` ORDER BY `c1` DESC, `c2` ASC'; + $internalTests24 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processOrder' => [[['`c1`', Select::ORDER_DESCENDING], ['`c2`', Select::ORDER_ASCENDING]]], + ]; + + $select25 = new Select(); + $select25->from('foo')->order(['c1' => 'asc'])->order('c2 desc'); // notice partially lower case ASC + $sqlPrep25 = // same + $sqlStr25 = 'SELECT `foo`.* FROM `foo` ORDER BY `c1` ASC, `c2` DESC'; + $internalTests25 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processOrder' => [[['`c1`', Select::ORDER_ASCENDING], ['`c2`', Select::ORDER_DESCENDING]]], + ]; + + // limit + $select26 = new Select(); + $select26->from('foo')->limit(5); + $sqlPrep26 = 'SELECT `foo`.* FROM `foo` LIMIT ?'; + $sqlStr26 = 'SELECT `foo`.* FROM `foo` LIMIT \'5\''; + $params26 = ['limit' => 5]; + $internalTests26 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processLimit' => ['?'], + ]; + + // limit with offset + $select27 = new Select(); + $select27->from('foo')->limit(5)->offset(10); + $sqlPrep27 = 'SELECT `foo`.* FROM `foo` LIMIT ? OFFSET ?'; + $sqlStr27 = 'SELECT `foo`.* FROM `foo` LIMIT \'5\' OFFSET \'10\''; + $params27 = ['limit' => 5, 'offset' => 10]; + $internalTests27 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; + + // joins with a few keywords in the on clause + $select28 = new Select(); + $select28->from('foo')->join('zac', '(m = n AND c.x) BETWEEN x AND y.z OR (c.x < y.z AND c.x <= y.z AND c.x > y.z AND c.x >= y.z)'); + $sqlPrep28 = // same + $sqlStr28 = 'SELECT `foo`.*, `zac`.* FROM `foo` INNER JOIN `zac` ON (`m` = `n` AND `c`.`x`) BETWEEN `x` AND `y`.`z` OR (`c`.`x` < `y`.`z` AND `c`.`x` <= `y`.`z` AND `c`.`x` > `y`.`z` AND `c`.`x` >= `y`.`z`)'; + $internalTests28 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.*']], '`foo`'], + 'processJoins' => [[['INNER', '`zac`', '(`m` = `n` AND `c`.`x`) BETWEEN `x` AND `y`.`z` OR (`c`.`x` < `y`.`z` AND `c`.`x` <= `y`.`z` AND `c`.`x` > `y`.`z` AND `c`.`x` >= `y`.`z`)']]], + ]; + + // order with compound name + $select29 = new Select(); + $select29->from('foo')->order('c1.d2'); + $sqlPrep29 = + $sqlStr29 = 'SELECT `foo`.* FROM `foo` ORDER BY `c1`.`d2` ASC'; + $internalTests29 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processOrder' => [[['`c1`.`d2`', Select::ORDER_ASCENDING]]], + ]; + + // group with compound name + $select30 = new Select(); + $select30->from('foo')->group('c1.d2'); + $sqlPrep30 = // same + $sqlStr30 = 'SELECT `foo`.* FROM `foo` GROUP BY `c1`.`d2`'; + $internalTests30 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processGroup' => [['`c1`.`d2`']], + ]; + + // join with expression in ON part + $select31 = new Select(); + $select31->from('foo')->join('zac', new Predicate\Expression('(m = n AND c.x) BETWEEN x AND y.z')); + $sqlPrep31 = // same + $sqlStr31 = 'SELECT `foo`.*, `zac`.* FROM `foo` INNER JOIN `zac` ON (m = n AND c.x) BETWEEN x AND y.z'; + $internalTests31 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.*']], '`foo`'], + 'processJoins' => [[['INNER', '`zac`', '(m = n AND c.x) BETWEEN x AND y.z']]], + ]; + + $select32subselect = new Select(); + $select32subselect->from('bar')->where->like('y', '%Foo%'); + $select32 = new Select(); + $select32->from(['x' => $select32subselect]); + $sqlPrep32 = 'SELECT `x`.* FROM (SELECT `bar`.* FROM `bar` WHERE `y` LIKE ?) AS `x`'; + $sqlStr32 = 'SELECT `x`.* FROM (SELECT `bar`.* FROM `bar` WHERE `y` LIKE \'%Foo%\') AS `x`'; + $internalTests32 = [ + 'processSelect' => [[['`x`.*']], '(SELECT `bar`.* FROM `bar` WHERE `y` LIKE ?) AS `x`'], + ]; + + $select33 = new Select(); + $select33->from('table')->columns(['*'])->where([ + 'c1' => null, + 'c2' => [1, 2, 3], + new IsNotNull('c3'), + ]); + $sqlPrep33 = 'SELECT `table`.* FROM `table` WHERE `c1` IS NULL AND `c2` IN (?, ?, ?) AND `c3` IS NOT NULL'; + $sqlStr33 = 'SELECT `table`.* FROM `table` WHERE `c1` IS NULL AND `c2` IN (\'1\', \'2\', \'3\') AND `c3` IS NOT NULL'; + $internalTests33 = [ + 'processSelect' => [[['`table`.*']], '`table`'], + 'processWhere' => ['`c1` IS NULL AND `c2` IN (?, ?, ?) AND `c3` IS NOT NULL'], + ]; + + // @author Demian Katz + $select34 = new Select(); + $select34->from('table')->order([ + new Expression('isnull(?) DESC', [['name' => ExpressionInterface::TYPE_IDENTIFIER]]), + 'name', + ]); + $sqlPrep34 = 'SELECT `table`.* FROM `table` ORDER BY isnull(`name`) DESC, `name` ASC'; + $sqlStr34 = 'SELECT `table`.* FROM `table` ORDER BY isnull(`name`) DESC, `name` ASC'; + $internalTests34 = [ + 'processOrder' => [[['isnull(`name`) DESC'], ['`name`', Select::ORDER_ASCENDING]]], + ]; + + // join with Expression object in COLUMNS part (Laminas-514) + // @co-author Koen Pieters (kpieters) + $select35 = new Select(); + $select35->from('foo')->columns([])->join('bar', 'm = n', ['thecount' => new Expression('COUNT(*)')]); + $sqlPrep35 = // same + $sqlStr35 = 'SELECT COUNT(*) AS `thecount` FROM `foo` INNER JOIN `bar` ON `m` = `n`'; + $internalTests35 = [ + 'processSelect' => [[['COUNT(*)', '`thecount`']], '`foo`'], + 'processJoins' => [[['INNER', '`bar`', '`m` = `n`']]], + ]; + + // multiple joins with expressions + // reported by @jdolieslager + $select36 = new Select(); + $select36->from('foo') + ->join('tableA', new Predicate\Operator('id', '=', 1)) + ->join('tableB', new Predicate\Operator('id', '=', 2)) + ->join('tableC', new Predicate\PredicateSet([ + new Predicate\Operator('id', '=', 3), + new Predicate\Operator('number', '>', 20), + ])); + $sqlPrep36 = 'SELECT `foo`.*, `tableA`.*, `tableB`.*, `tableC`.* FROM `foo`' + . ' INNER JOIN `tableA` ON `id` = :join1part1 INNER JOIN `tableB` ON `id` = :join2part1 ' + . 'INNER JOIN `tableC` ON `id` = :join3part1 AND `number` > :join3part2'; + $sqlStr36 = 'SELECT `foo`.*, `tableA`.*, `tableB`.*, `tableC`.* FROM `foo` ' + . 'INNER JOIN `tableA` ON `id` = \'1\' INNER JOIN `tableB` ON `id` = \'2\' ' + . 'INNER JOIN `tableC` ON `id` = \'3\' AND `number` > \'20\''; + $internalTests36 = []; + + /** + * @link https://github.com/zendframework/zf2/pull/2714 + */ + $select37 = new Select(); + $select37->from('foo')->columns(['bar'], false); + $sqlPrep37 = // same + $sqlStr37 = 'SELECT `bar` AS `bar` FROM `foo`'; + $internalTests37 = [ + 'processSelect' => [[['`bar`', '`bar`']], '`foo`'], + ]; + + // @link https://github.com/zendframework/zf2/issues/3294 + // Test TableIdentifier In Joins + $select38 = new Select(); + $select38->from('foo')->columns([]) + ->join(new TableIdentifier('bar', 'baz'), 'm = n', ['thecount' => new Expression('COUNT(*)')]); + $sqlPrep38 = // same + $sqlStr38 = 'SELECT COUNT(*) AS `thecount` FROM `foo` INNER JOIN `baz`.`bar` ON `m` = `n`'; + $internalTests38 = [ + 'processSelect' => [[['COUNT(*)', '`thecount`']], '`foo`'], + 'processJoins' => [[['INNER', '`baz`.`bar`', '`m` = `n`']]], + ]; + + // subselect in join + $select39subselect = new Select(); + $select39subselect->from('bar')->where->like('y', '%Foo%'); + $select39 = new Select(); + $select39->from('foo')->join(['z' => $select39subselect], 'z.foo = bar.id'); + $sqlPrep39 = 'SELECT `foo`.*, `z`.* FROM `foo` INNER JOIN (SELECT `bar`.* FROM `bar` WHERE `y` LIKE ?) AS `z` ON `z`.`foo` = `bar`.`id`'; + $sqlStr39 = 'SELECT `foo`.*, `z`.* FROM `foo` INNER JOIN (SELECT `bar`.* FROM `bar` WHERE `y` LIKE \'%Foo%\') AS `z` ON `z`.`foo` = `bar`.`id`'; + $internalTests39 = [ + 'processJoins' => [ + [['INNER', '(SELECT `bar`.* FROM `bar` WHERE `y` LIKE ?) AS `z`', '`z`.`foo` = `bar`.`id`']], + ], + ]; + + // @link https://github.com/zendframework/zf2/issues/3294 + // Test TableIdentifier In Joins, with multiple joins + $select40 = new Select(); + $select40->from('foo') + ->join(['a' => new TableIdentifier('another_foo', 'another_schema')], 'a.x = foo.foo_column') + ->join('bar', 'foo.colx = bar.colx'); + $sqlPrep40 = // same + $sqlStr40 = 'SELECT `foo`.*, `a`.*, `bar`.* FROM `foo`' + . ' INNER JOIN `another_schema`.`another_foo` AS `a` ON `a`.`x` = `foo`.`foo_column`' + . ' INNER JOIN `bar` ON `foo`.`colx` = `bar`.`colx`'; + $internalTests40 = [ + 'processSelect' => [[['`foo`.*'], ['`a`.*'], ['`bar`.*']], '`foo`'], + 'processJoins' => [ + [ + ['INNER', '`another_schema`.`another_foo` AS `a`', '`a`.`x` = `foo`.`foo_column`'], + ['INNER', '`bar`', '`foo`.`colx` = `bar`.`colx`'], + ], + ], + ]; + + $select41 = new Select(); + $select41->from('foo')->quantifier(Select::QUANTIFIER_DISTINCT); + $sqlPrep41 = // same + $sqlStr41 = 'SELECT DISTINCT `foo`.* FROM `foo`'; + $internalTests41 = [ + 'processSelect' => [Select::QUANTIFIER_DISTINCT, [['`foo`.*']], '`foo`'], + ]; + + $select42 = new Select(); + $select42->from('foo')->quantifier(new Expression('TOP ?', [10])); + $sqlPrep42 = 'SELECT TOP ? `foo`.* FROM `foo`'; + $sqlStr42 = 'SELECT TOP \'10\' `foo`.* FROM `foo`'; + $internalTests42 = [ + 'processSelect' => ['TOP ?', [['`foo`.*']], '`foo`'], + ]; + + $select43 = new Select(); + $select43->from(['x' => 'foo'])->columns(['bar' => 'foo.bar'], false); + $sqlPrep43 = 'SELECT `foo`.`bar` AS `bar` FROM `foo` AS `x`'; + $sqlStr43 = 'SELECT `foo`.`bar` AS `bar` FROM `foo` AS `x`'; + $internalTests43 = [ + 'processSelect' => [[['`foo`.`bar`', '`bar`']], '`foo` AS `x`'], + ]; + + $select44 = new Select(); + $select44->from('foo')->where('a = b'); + $select44b = new Select(); + $select44b->from('bar')->where('c = d'); + $select44->combine($select44b, Select::COMBINE_UNION, 'ALL'); + $sqlPrep44 = // same + $sqlStr44 = '( SELECT `foo`.* FROM `foo` WHERE a = b ) UNION ALL ( SELECT `bar`.* FROM `bar` WHERE c = d )'; + $internalTests44 = [ + 'processCombine' => ['UNION ALL', 'SELECT `bar`.* FROM `bar` WHERE c = d'], + ]; + + // limit with offset + $select45 = new Select(); + $select45->from('foo')->limit('5')->offset('10'); + $sqlPrep45 = 'SELECT `foo`.* FROM `foo` LIMIT ? OFFSET ?'; + $sqlStr45 = 'SELECT `foo`.* FROM `foo` LIMIT \'5\' OFFSET \'10\''; + $params45 = ['limit' => '5', 'offset' => '10']; + $internalTests45 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; + + // functions without table + $select46 = new Select(); + $select46->columns([ + new Expression('SOME_DB_FUNCTION_ONE()'), + 'foo' => new Expression('SOME_DB_FUNCTION_TWO()'), + ]); + $sqlPrep46 = 'SELECT SOME_DB_FUNCTION_ONE() AS Expression1, SOME_DB_FUNCTION_TWO() AS `foo`'; + $sqlStr46 = 'SELECT SOME_DB_FUNCTION_ONE() AS Expression1, SOME_DB_FUNCTION_TWO() AS `foo`'; + $params46 = []; + $internalTests46 = []; + + // limit with big offset and limit + $select47 = new Select(); + $select47->from('foo')->limit('10000000000000000000')->offset('10000000000000000000'); + $sqlPrep47 = 'SELECT `foo`.* FROM `foo` LIMIT ? OFFSET ?'; + $sqlStr47 = 'SELECT `foo`.* FROM `foo` LIMIT \'10000000000000000000\' OFFSET \'10000000000000000000\''; + $params47 = ['limit' => '10000000000000000000', 'offset' => '10000000000000000000']; + $internalTests47 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; + + //combine and union with order at the end + $select48 = new Select(); + $select48->from('foo')->where('a = b'); + $select48b = new Select(); + $select48b->from('bar')->where('c = d'); + $select48->combine($select48b); + + $select48combined = new Select(); + $select48 = $select48combined->from(['sub' => $select48])->order('id DESC'); + $sqlPrep48 = // same + $sqlStr48 = 'SELECT `sub`.* FROM (( SELECT `foo`.* FROM `foo` WHERE a = b ) UNION ( SELECT `bar`.* FROM `bar` WHERE c = d )) AS `sub` ORDER BY `id` DESC'; + $internalTests48 = [ + 'processCombine' => null, + ]; + + //Expression as joinName + // $select49 = new Select(); + // $select49->from(new TableIdentifier('foo')) + // ->join(['bar' => new Expression('psql_function_which_returns_table')], 'foo.id = bar.fooid'); + // $sqlPrep49 = // same + // $sqlStr49 = 'SELECT `foo`.*, `bar`.* FROM `foo` INNER JOIN psql_function_which_returns_table AS `bar` ON `foo`.`id` = `bar`.`fooid`'; + // $internalTests49 = [ + // 'processSelect' => [[['`foo`.*'], ['`bar`.*']], '`foo`'], + // 'processJoins' => [[['INNER', 'psql_function_which_returns_table AS `bar`', '`foo`.`id` = `bar`.`fooid`']]], + // ]; + + // Test generic predicate is appended with AND + $select50 = new Select(); + $select50->from(new TableIdentifier('foo')) + ->where + ->nest + ->isNull('bar') + ->and + ->predicate(new Predicate\Literal('1=1')) + ->unnest; + $sqlPrep50 = // same + $sqlStr50 = 'SELECT `foo`.* FROM `foo` WHERE (`bar` IS NULL AND 1=1)'; + $internalTests50 = []; + + // Test generic predicate is appended with OR + $select51 = new Select(); + $select51->from(new TableIdentifier('foo')) + ->where + ->nest + ->isNull('bar') + ->or + ->predicate(new Predicate\Literal('1=1')) + ->unnest; + $sqlPrep51 = // same + $sqlStr51 = 'SELECT `foo`.* FROM `foo` WHERE (`bar` IS NULL OR 1=1)'; + $internalTests51 = []; + + /** + * @link https://github.com/zendframework/zf2/issues/7222 + */ + $select52 = new Select(); + $select52->from('foo')->join('zac', '(catalog_category_website.category_id = catalog_category.category_id)'); + $sqlPrep52 = // same + $sqlStr52 = 'SELECT `foo`.*, `zac`.* FROM `foo` INNER JOIN `zac` ON (`catalog_category_website`.`category_id` = `catalog_category`.`category_id`)'; + $internalTests52 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.*']], '`foo`'], + 'processJoins' => [ + [ + ['INNER', '`zac`', '(`catalog_category_website`.`category_id` = `catalog_category`.`category_id`)'], + ], + ], + ]; + + $subSelect53 = new Select(); + $subSelect53->from('bar')->columns(['id'])->limit(10)->offset(9); + $select53 = new Select(); + $select53->from('foo')->where(new In('bar_id', $subSelect53))->limit(11)->offset(12); + $params53 = ['limit' => 11, 'offset' => 12, 'subselect1limit' => 10, 'subselect1offset' => 9]; + $sqlPrep53 = 'SELECT `foo`.* FROM `foo` WHERE `bar_id` IN (SELECT `bar`.`id` AS `id` FROM `bar` LIMIT :subselect1limit OFFSET :subselect1offset) LIMIT :limit OFFSET :offset'; + $sqlStr53 = 'SELECT `foo`.* FROM `foo` WHERE `bar_id` IN (SELECT `bar`.`id` AS `id` FROM `bar` LIMIT \'10\' OFFSET \'9\') LIMIT \'11\' OFFSET \'12\''; + $internalTests53 = [ + 'processSelect' => [[['`foo`.*']], '`foo`'], + 'processWhere' => ['`bar_id` IN (SELECT `bar`.`id` AS `id` FROM `bar` LIMIT ? OFFSET ?)'], + 'processLimit' => ['?'], + 'processOffset' => ['?'], + ]; + + // join with alternate type full outer + $select54 = new Select(); + $select54->from('foo')->join('zac', 'm = n', ['bar', 'baz'], Select::JOIN_FULL_OUTER); + $sqlPrep54 = // same + $sqlStr54 = 'SELECT `foo`.*, `zac`.`bar` AS `bar`, `zac`.`baz` AS `baz` FROM `foo` FULL OUTER JOIN `zac` ON `m` = `n`'; + $internalTests54 = [ + 'processSelect' => [[['`foo`.*'], ['`zac`.`bar`', '`bar`'], ['`zac`.`baz`', '`baz`']], '`foo`'], + 'processJoins' => [[['FULL OUTER', '`zac`', '`m` = `n`']]], + ]; + + /** + * $select = the select object + * $sqlPrep = the sql as a result of preparation + * $params = the param container contents result of preparation + * $sqlStr = the sql as a result of getting a string back + * $internalTests what the internal functions should return (safe-guarding extension) + */ + + return [ + // $select $sqlPrep $params $sqlStr $internalTests use named param + [$select0, $sqlPrep0, [], $sqlStr0, $internalTests0, false], + [$select1, $sqlPrep1, [], $sqlStr1, $internalTests1, false], + [$select2, $sqlPrep2, [], $sqlStr2, $internalTests2, false], + [$select3, $sqlPrep3, [], $sqlStr3, $internalTests3, false], + [$select4, $sqlPrep4, [], $sqlStr4, $internalTests4, false], + [$select5, $sqlPrep5, [], $sqlStr5, $internalTests5, false], + [$select6, $sqlPrep6, [], $sqlStr6, $internalTests6, false], + [$select7, $sqlPrep7, [], $sqlStr7, $internalTests7, false], + [$select8, $sqlPrep8, [], $sqlStr8, $internalTests8, false], + [$select9, $sqlPrep9, $params9, $sqlStr9, $internalTests9, false], + [$select10, $sqlPrep10, [], $sqlStr10, $internalTests10, false], + [$select11, $sqlPrep11, [], $sqlStr11, $internalTests11, false], + [$select12, $sqlPrep12, [], $sqlStr12, $internalTests12, false], + [$select13, $sqlPrep13, [], $sqlStr13, $internalTests13, false], + [$select14, $sqlPrep14, [], $sqlStr14, $internalTests14, false], + [$select15, $sqlPrep15, [], $sqlStr15, $internalTests15, false], + [$select16, $sqlPrep16, $params16, $sqlStr16, $internalTests16, false], + [$select17, $sqlPrep17, [], $sqlStr17, $internalTests17, false], + [$select18, $sqlPrep18, [], $sqlStr18, $internalTests18, false], + [$select19, $sqlPrep19, [], $sqlStr19, $internalTests19, false], + [$select20, $sqlPrep20, [], $sqlStr20, $internalTests20, false], + [$select21, $sqlPrep21, $params21, $sqlStr21, $internalTests21, false], + [$select22, $sqlPrep22, [], $sqlStr22, $internalTests22, false], + [$select23, $sqlPrep23, [], $sqlStr23, $internalTests23, false], + [$select24, $sqlPrep24, [], $sqlStr24, $internalTests24, false], + [$select25, $sqlPrep25, [], $sqlStr25, $internalTests25, false], + [$select26, $sqlPrep26, $params26, $sqlStr26, $internalTests26, false], + [$select27, $sqlPrep27, $params27, $sqlStr27, $internalTests27, false], + [$select28, $sqlPrep28, [], $sqlStr28, $internalTests28, false], + [$select29, $sqlPrep29, [], $sqlStr29, $internalTests29, false], + [$select30, $sqlPrep30, [], $sqlStr30, $internalTests30, false], + [$select31, $sqlPrep31, [], $sqlStr31, $internalTests31, false], + [$select32, $sqlPrep32, [], $sqlStr32, $internalTests32, false], + [$select33, $sqlPrep33, [], $sqlStr33, $internalTests33, false], + [$select34, $sqlPrep34, [], $sqlStr34, $internalTests34, false], + [$select35, $sqlPrep35, [], $sqlStr35, $internalTests35, false], + [$select36, $sqlPrep36, [], $sqlStr36, $internalTests36, true], + [$select37, $sqlPrep37, [], $sqlStr37, $internalTests37, false], + [$select38, $sqlPrep38, [], $sqlStr38, $internalTests38, false], + [$select39, $sqlPrep39, [], $sqlStr39, $internalTests39, false], + [$select40, $sqlPrep40, [], $sqlStr40, $internalTests40, false], + [$select41, $sqlPrep41, [], $sqlStr41, $internalTests41, false], + [$select42, $sqlPrep42, [], $sqlStr42, $internalTests42, false], + [$select43, $sqlPrep43, [], $sqlStr43, $internalTests43, false], + [$select44, $sqlPrep44, [], $sqlStr44, $internalTests44, false], + [$select45, $sqlPrep45, $params45, $sqlStr45, $internalTests45, false], + [$select46, $sqlPrep46, $params46, $sqlStr46, $internalTests46, false], + [$select47, $sqlPrep47, $params47, $sqlStr47, $internalTests47, false], + [$select48, $sqlPrep48, [], $sqlStr48, $internalTests48, false], + //[$select49, $sqlPrep49, [], $sqlStr49, $internalTests49, false], + [$select50, $sqlPrep50, [], $sqlStr50, $internalTests50, false], + [$select51, $sqlPrep51, [], $sqlStr51, $internalTests51, false], + [$select52, $sqlPrep52, [], $sqlStr52, $internalTests52, false], + [$select53, $sqlPrep53, $params53, $sqlStr53, $internalTests53, true], + [$select54, $sqlPrep54, [], $sqlStr54, $internalTests54, false], + ]; + // phpcs:enable Generic.Files.LineLength.TooLong + } +} diff --git a/test/unit/TestAsset/DeleteIgnore.php b/test/unit/TestAsset/DeleteIgnore.php new file mode 100644 index 0000000..4deb6ab --- /dev/null +++ b/test/unit/TestAsset/DeleteIgnore.php @@ -0,0 +1,27 @@ + */ + protected $specifications = [ + self::SPECIFICATION_DELETE => 'DELETE IGNORE FROM %1$s', + self::SPECIFICATION_WHERE => 'WHERE %1$s', + ]; + + protected function processdeleteIgnore( + PlatformInterface $platform, + ?DriverInterface $driver = null, + ?ParameterContainer $parameterContainer = null + ): string { + return parent::processDelete($platform, $driver, $parameterContainer); + } +} diff --git a/test/unit/TestAsset/TrustingMysqlPlatform.php b/test/unit/TestAsset/TrustingMysqlPlatform.php new file mode 100644 index 0000000..7a9a8de --- /dev/null +++ b/test/unit/TestAsset/TrustingMysqlPlatform.php @@ -0,0 +1,21 @@ +