From bfa360ce2499e062395631c2475d20ea1472aa72 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sat, 9 Mar 2019 18:53:33 +0100 Subject: [PATCH] Get subdomain --- DataStorage/File/JsonBuilder.php | 1159 ++++++++++++++++++++ DataStorage/File/JsonGrammar.php | 5 + DataStorage/File/QueryType.php | 34 + Uri/Http.php | 19 + tests/DataStorage/File/JsonBuilderTest.php | 153 +++ tests/DataStorage/File/testDb1.json | 36 + tests/DataStorage/File/testDb2.json | 47 + 7 files changed, 1453 insertions(+) create mode 100644 DataStorage/File/JsonBuilder.php create mode 100644 DataStorage/File/JsonGrammar.php create mode 100644 DataStorage/File/QueryType.php create mode 100644 tests/DataStorage/File/JsonBuilderTest.php create mode 100644 tests/DataStorage/File/testDb1.json create mode 100644 tests/DataStorage/File/testDb2.json diff --git a/DataStorage/File/JsonBuilder.php b/DataStorage/File/JsonBuilder.php new file mode 100644 index 000000000..25961948f --- /dev/null +++ b/DataStorage/File/JsonBuilder.php @@ -0,0 +1,1159 @@ +', + '<=', + '>=', + '<>', + '!=', + 'like', + 'like binary', + 'not like', + 'between', + 'ilike', + '&', + '|', + '^', + '<<', + '>>', + 'rlike', + 'regexp', + 'not regexp', + '~', + '~*', + '!~', + '!~*', + 'similar to', + 'not similar to', + 'in', + ]; + + /** + * Json grammar. + * + * @var JsonGrammar + * @since 1.0.0 + */ + private $grammar = null; + + /** + * Constructor. + * + * @param bool $readOnly Query is read only + * + * @since 1.0.0 + */ + public function __construct(bool $readOnly = false) + { + $this->isReadOnly = $readOnly; + $this->grammar = new JsonGrammar(); + } + + /** + * Select. + * + * @param array ...$columns Columns + * + * @return JsonBuilder + * + * @todo Closure is not working this way, needs to be evaluated befor assigning + * + * @since 1.0.0 + */ + public function select(...$columns) : self + { + $this->type = QueryType::SELECT; + + foreach ($columns as $key => $column) { + if (\is_string($column) || $column instanceof \Closure) { + $this->selects[] = $column; + } else { + throw new \InvalidArgumentException(); + } + } + + return $this; + } + + /** + * Select. + * + * @param array ...$columns Columns + * + * @return JsonBuilder + * + * @todo Closure is not working this way, needs to be evaluated befor assigning + * + * @since 1.0.0 + */ + public function random(...$columns) : self + { + $this->select(...$columns); + + $this->type = QueryType::RANDOM; + + return $this; + } + + /** + * Bind parameter. + * + * @param mixed $binds Binds + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function bind($binds) : self + { + if (\is_array($binds)) { + $this->binds += $binds; + } elseif (\is_string($binds) || $binds instanceof \Closure) { + $this->binds[] = $binds; + } else { + throw new \InvalidArgumentException(); + } + + return $this; + } + + /** + * Creating new. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function newQuery() : self + { + return new static($this->connection, $this->isReadOnly); + } + + /** + * Is distinct. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function distinct() : self + { + $this->distinct = true; + + return $this; + } + + /** + * From. + * + * @param array ...$tables Tables + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function from(...$tables) : self + { + foreach ($tables as $key => $table) { + if (\is_string($table) || $table instanceof \Closure) { + $this->from[] = $table; + } else { + throw new \InvalidArgumentException(); + } + } + + return $this; + } + + /** + * Where. + * + * @param array|\Closure|string|Where $columns Columns + * @param array|string $operator Operator + * @param mixed $values Values + * @param array|string $boolean Boolean condition + * + * @return JsonBuilder + * + * @throws \InvalidArgumentException + * + * @since 1.0.0 + */ + public function where($columns, $operator = null, $values = null, $boolean = 'and') : self + { + if (!\is_array($columns)) { + $columns = [$columns]; + $operator = [$operator]; + $values = [$values]; + $boolean = [$boolean]; + } + + $i = 0; + foreach ($columns as $key => $column) { + if (isset($operator[$i]) && !\in_array(\strtolower($operator[$i]), self::OPERATORS)) { + throw new \InvalidArgumentException('Unknown operator.'); + } + + $this->wheres[$column][] = [ + 'column' => $column, + 'operator' => $operator[$i], + 'value' => $values[$i], + 'boolean' => $boolean[$i], + ]; + + ++$i; + } + + return $this; + } + + /** + * Get column of where condition + * + * One column can have multiple where conditions. + * TODO: maybe think about a case where there is a where condition but no column but some other identifier? + * + * @param mixed $column Column + * + * @return null|array + * + * @since 1.0.0 + */ + public function getWhereByColumn($column) : ?array + { + return $this->wheres[$column] ?? null; + } + + /** + * Where and sub condition. + * + * @param array|\Closure|string|Where $where Where sub condition + * @param mixed $operator Operator + * @param mixed $values Values + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function andWhere($where, $operator = null, $values = null) : self + { + return $this->where($where, $operator, $values, 'and'); + } + + /** + * Where or sub condition. + * + * @param array|\Closure|string|Where $where Where sub condition + * @param mixed $operator Operator + * @param mixed $values Values + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function orWhere($where, $operator = null, $values = null) : self + { + return $this->where($where, $operator, $values, 'or'); + } + + /** + * Where in. + * + * @param array|\Closure|string|Where $column Column + * @param mixed $values Values + * @param string $boolean Boolean condition + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function whereIn($column, $values = null, string $boolean = 'and') : self + { + $this->where($column, 'in', $values, $boolean); + + return $this; + } + + /** + * Where null. + * + * @param array|\Closure|string|Where $column Column + * @param string $boolean Boolean condition + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function whereNull($column, string $boolean = 'and') : self + { + $this->where($column, '=', null, $boolean); + + return $this; + } + + /** + * Where not null. + * + * @param array|\Closure|string|Where $column Column + * @param string $boolean Boolean condition + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function whereNotNull($column, string $boolean = 'and') : self + { + $this->where($column, '!=', null, $boolean); + + return $this; + } + + /** + * Group by. + * + * @param array|\Closure|string ...$columns Grouping result + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function groupBy(...$columns) : self + { + foreach ($columns as $key => $column) { + if (\is_string($column) || $column instanceof \Closure) { + $this->groups[] = $column; + } else { + throw new \InvalidArgumentException(); + } + } + + return $this; + } + + /** + * Order by newest. + * + * @param \Closure|string $column Column + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function newest($column) : self + { + $this->orderBy($column, 'DESC'); + + return $this; + } + + /** + * Order by oldest. + * + * @param \Closure|string $column Column + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function oldest($column) : self + { + $this->orderBy($column, 'ASC'); + + return $this; + } + + /** + * Order by oldest. + * + * @param array|\Closure|string $columns Columns + * @param string|string[] $order Orders + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function orderBy($columns, $order = 'DESC') : self + { + if (\is_string($columns) || $columns instanceof \Closure) { + if (!\is_string($order)) { + throw new \InvalidArgumentException(); + } + + if (!isset($this->orders[$order])) { + $this->orders[$order] = []; + } + + $this->orders[$order][] = $columns; + } elseif (\is_array($columns)) { + foreach ($columns as $key => $column) { + $this->orders[\is_string($order) ? $order : $order[$key]][] = $column; + } + } else { + throw new \InvalidArgumentException(); + } + + return $this; + } + + /** + * Offset. + * + * @param int $offset Offset + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function offset(int $offset) : self + { + $this->offset = $offset; + + return $this; + } + + /** + * Limit. + * + * @param int $limit Limit + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function limit(int $limit) : self + { + $this->limit = $limit; + + return $this; + } + + /** + * Union. + * + * @param mixed $query Query + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function union($query) : self + { + if (!\is_array($query)) { + $this->unions[] = $query; + } else { + $this->unions += $query; + } + + return $this; + } + + /** + * Lock query. + * + * @return void + * + * @since 1.0.0 + */ + public function lock() : void + { + } + + /** + * Lock for update query. + * + * @return void + * + * @since 1.0.0 + */ + public function lockUpdate() : void + { + } + + /** + * Find query. + * + * @return void + * + * @since 1.0.0 + */ + public function find() : void + { + } + + /** + * Count results. + * + * @param string $table Table to count the result set + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function count(string $table = '*') : self + { + // todo: don't do this as string, create new object new \count(); this can get handled by the grammar parser WAY better + return $this->select('COUNT(' . $table . ')'); + } + + /** + * Select minimum. + * + * @return void + * + * @since 1.0.0 + */ + public function min() : void + { + } + + /** + * Select maximum. + * + * @return void + * + * @since 1.0.0 + */ + public function max() : void + { + } + + /** + * Select sum. + * + * @return void + * + * @since 1.0.0 + */ + public function sum() : void + { + } + + /** + * Select average. + * + * @return void + * + * @since 1.0.0 + */ + public function avg() : void + { + } + + /** + * Insert into columns. + * + * @param array ...$columns Columns + * + * @return JsonBuilder + * + * @throws \Exception + * + * @since 1.0.0 + */ + public function insert(...$columns) : self + { + if ($this->isReadOnly) { + throw new \Exception(); + } + + $this->type = QueryType::INSERT; + + foreach ($columns as $key => $column) { + $this->inserts[] = $column; + } + + return $this; + } + + /** + * Table to insert into. + * + * @param \Closure|string $table Table + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function into($table) : self + { + $this->into = $table; + + return $this; + } + + /** + * Values to insert. + * + * @param array ...$values Values + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function values(...$values) : self + { + $this->values[] = $values; + + return $this; + } + + /** + * Get insert values + * + * @return array + * + * @since 1.0.0 + */ + public function getValues() : array + { + return $this->values; + } + + /** + * Values to insert. + * + * @param mixed $value Values + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function value($value) : self + { + \end($this->values); + $key = \key($this->values); + + if (\is_array($value)) { + $this->values[$key] = $value; + } else { + $this->values[$key][] = $value; + } + + \reset($this->values); + + return $this; + } + + /** + * Values to insert. + * + * @param array ...$sets Values + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function sets(...$sets) : self + { + $this->sets[$sets[0]] = $sets[1] ?? null; + + return $this; + } + + /** + * Values to insert. + * + * @param mixed $set Values + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function set($set) : self + { + $this->sets[\key($set)] = \current($set); + + return $this; + } + + /** + * Update columns. + * + * @param array ...$tables Column names to update + * + * @return JsonBuilder + * + * @throws \Exception + * + * @since 1.0.0 + */ + public function update(...$tables) : self + { + if ($this->isReadOnly) { + throw new \Exception(); + } + + $this->type = QueryType::UPDATE; + + foreach ($tables as $key => $table) { + if (\is_string($table) || $table instanceof \Closure) { + $this->updates[] = $table; + } else { + throw new \InvalidArgumentException(); + } + } + + return $this; + } + + /** + * Delete query + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function delete() : self + { + if ($this->isReadOnly) { + throw new \Exception(); + } + + $this->type = QueryType::DELETE; + + return $this; + } + + /** + * Increment value. + * + * @return void + * + * @since 1.0.0 + */ + public function increment() : void + { + } + + /** + * Decrement value. + * + * @return void + * + * @since 1.0.0 + */ + public function decrement() : void + { + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function join($table, string $type = JoinType::JOIN) : self + { + if (\is_string($table) || $table instanceof \Closure) { + $this->joins[] = ['type' => $type, 'table' => $table]; + } else { + throw new \InvalidArgumentException(); + } + + return $this; + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function leftJoin($column) : self + { + return $this->join($column, JoinType::LEFT_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function leftOuterJoin($column) : self + { + return $this->join($column, JoinType::LEFT_OUTER_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function leftInnerJoin($column) : self + { + return $this->join($column, JoinType::LEFT_INNER_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function rightJoin($column) : self + { + return $this->join($column, JoinType::RIGHT_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function rightOuterJoin($column) : self + { + return $this->join($column, JoinType::RIGHT_OUTER_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function rightInnerJoin($column) : self + { + return $this->join($column, JoinType::RIGHT_INNER_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function outerJoin($column) : self + { + return $this->join($column, JoinType::OUTER_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function innerJoin($column) : self + { + return $this->join($column, JoinType::INNER_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function crossJoin($column) : self + { + return $this->join($column, JoinType::CROSS_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function fullJoin($column) : self + { + return $this->join($column, JoinType::FULL_JOIN); + } + + /** + * Join. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function fullOuterJoin($column) : self + { + return $this->join($column, JoinType::FULL_OUTER_JOIN); + } + + /** + * On. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function on($columns, $operator = null, $values = null, $boolean = 'and') : self + { + if ($operator !== null && !\is_array($operator) && !\in_array(\strtolower($operator), self::OPERATORS)) { + throw new \InvalidArgumentException('Unknown operator.'); + } + + if (!\is_array($columns)) { + $columns = [$columns]; + $operator = [$operator]; + $values = [$values]; + $boolean = [$boolean]; + } + + $joinCount = \count($this->joins) - 1; + $i = 0; + + foreach ($columns as $key => $column) { + if (isset($operator[$i]) && !\in_array(\strtolower($operator[$i]), self::OPERATORS)) { + throw new \InvalidArgumentException('Unknown operator.'); + } + + $this->ons[$joinCount][] = [ + 'column' => $column, + 'operator' => $operator[$i], + 'value' => $values[$i], + 'boolean' => $boolean[$i], + ]; + + ++$i; + } + + return $this; + } + + /** + * On. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function orOn($columns, $operator = null, $values = null) : self + { + return $this->on($columns, $operator, $values, 'or'); + } + + /** + * On. + * + * @return JsonBuilder + * + * @since 1.0.0 + */ + public function andOn($columns, $operator = null, $values = null) : self + { + return $this->on($columns, $operator, $values, 'and'); + } + + /** + * Execute query. + * + * @return mixed + * + * @since 1.0.0 + */ + public function execute() + { + return $this->grammar->compileQuery($this); + } +} \ No newline at end of file diff --git a/DataStorage/File/JsonGrammar.php b/DataStorage/File/JsonGrammar.php new file mode 100644 index 000000000..f34697c32 --- /dev/null +++ b/DataStorage/File/JsonGrammar.php @@ -0,0 +1,5 @@ +host; } + /** + * Return the subdomain of a host + * + * @return string + * + * @since 1.0.0 + */ + public function getSubdomain() : string + { + $host = explode('.', $this->host); + $length = \count($host) - 2; + + if ($length < 1) { + return ''; + } + + return \array_slice($host, 0, $length); + } + /** * {@inheritdoc} */ diff --git a/tests/DataStorage/File/JsonBuilderTest.php b/tests/DataStorage/File/JsonBuilderTest.php new file mode 100644 index 000000000..ef386ad00 --- /dev/null +++ b/tests/DataStorage/File/JsonBuilderTest.php @@ -0,0 +1,153 @@ +table1 = \json_decode(\file_get_contents(__DIR__ . '/testDb1.json'), true); + $this->table2 = \json_decode(\file_get_contents(__DIR__ . '/testDb2.json'), true); + } + + public function testJsonSelect() : void + { + $this->markTestSkipped(); + + $query = new JsonBuilder(); + self::assertEquals('acc1', $query->select('/0/account/*/name')->from($this->table1, $this->table2)->where('/0/account/*/id', '=', 1)->execute()['name']); + self::assertEquals('acc6', $query->select('/1/account/*/name')->from($this->table1, $this->table2)->where('/1/account/*/id', '=', 2)->execute()['name']); + + //$query = new JsonBuilder(); + //self::assertEquals($sql, $query->select('a.test')->distinct()->from('a')->where('a.test', '=', 1)->execute()); + + $query = new JsonBuilder(); + $datetime = new \DateTime('1999-31-12'); + self::assertEquals('dog2', $query->select('/0/animals/dog')->from($this->table2)->where('/0/animals/dog/created', '>', $datetime)->execute()['name']); + + $table = $this->table2; + $query = new JsonBuilder(); + self::assertEquals(['dog1', 'dog2', 'cat2'], $query->select('/0/animals/dog/*/name', function () { + return '/0/animals/cat/*/name'; + })->from(function () use ($table) { + return $table; + })->where(['/0/animals/cat/*/owner', '/0/animals/dog/*/owner'], ['=', '='], [1, 4], ['and', 'or'])->execute()); + } + + public function testJsonOrder() : void + { + $this->markTestSkipped(); + + $query = new JsonBuilder(); + self::assertEquals(['acc2', 'acc1', 'acc4'], + $query->select('/0/account/*/name') + ->from($this->table1) + ->where('/0/account/*/id', '>', 0) + ->orderBy('/0/account/status', 'ASC') + ->execute() + ); + } + + public function testJsonOffsetLimit() : void + { + $this->markTestSkipped(); + + $query = new JsonBuilder(); + self::assertEquals(['acc2', 'acc1'], + $query->select('/0/account/*/name') + ->from($this->table1) + ->where('/0/account/*/id', '>', 0) + ->orderBy('/0/account/status', 'ASC') + ->limit(2) + ->execute() + ); + } + + /** + * @expectedException \Exception + */ + public function testReadOnlyInsert() : void + { + $query = new JsonBuilder(true); + $query->insert('test'); + } + + /** + * @expectedException \Exception + */ + public function testReadOnlyUpdate() : void + { + $query = new JsonBuilder(true); + $query->update(); + } + + /** + * @expectedException \Exception + */ + public function testReadOnlyDelete() : void + { + $query = new JsonBuilder(true); + $query->delete(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidWhereOperator() : void + { + $query = new JsonBuilder(true); + $query->where('a', 'invalid', 'b'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidJoinTable() : void + { + $query = new JsonBuilder(true); + $query->join(null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidJoinOperator() : void + { + $query = new JsonBuilder(true); + $query->join('b')->on('a', 'invalid', 'b'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidOrOrderType() : void + { + $query = new JsonBuilder(true); + $query->orderBy('a', 1); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidOrColumnType() : void + { + $query = new JsonBuilder(true); + $query->orderBy(null, 'DESC'); + } +} \ No newline at end of file diff --git a/tests/DataStorage/File/testDb1.json b/tests/DataStorage/File/testDb1.json new file mode 100644 index 000000000..aaa8d86f4 --- /dev/null +++ b/tests/DataStorage/File/testDb1.json @@ -0,0 +1,36 @@ +{ + "account": [ + { + "id": 1, + "name": "acc1", + "status": 2 + }, + { + "id": 2, + "name": "acc2", + "status": 1 + }, + { + "id": 4, + "name": "acc4", + "status": 2 + } + ], + "news": [ + { + "id": 1, + "title": "news1", + "by": 2 + }, + { + "id": 2, + "title": "news2", + "by": 4 + }, + { + "id": 4, + "title": "news4", + "by": 2 + } + ] +} \ No newline at end of file diff --git a/tests/DataStorage/File/testDb2.json b/tests/DataStorage/File/testDb2.json new file mode 100644 index 000000000..27702750e --- /dev/null +++ b/tests/DataStorage/File/testDb2.json @@ -0,0 +1,47 @@ +{ + "account": [ + { + "id": 1, + "name": "acc5", + "status": 2 + }, + { + "id": 2, + "name": "acc6", + "status": 1 + }, + { + "id": 4, + "name": "acc7", + "status": 2 + } + ], + "animals": { + "dog": [ + { + "id": 1, + "name": "dog1", + "owner": 4, + "created": "1999-01-01" + }, + { + "id": 2, + "name": "dog2", + "owner": 1, + "created": "2000-01-01" + } + ], + "cat": { + "1": { + "id": 1, + "name": "cat1", + "owner": 2 + }, + "2": { + "id": 2, + "name": "cat2", + "owner": 1 + } + } + } +} \ No newline at end of file