/** * Test whether we can use SQL filters and * whether they are being applied properly for * different types of SELECT queries in the QueryWriter. */ public function testSQLFilters() { R::nuke(); AQueryWriter::setSQLFilters(array(QueryWriter::C_SQLFILTER_READ => array('book' => array('title' => ' LOWER(book.title) ')), QueryWriter::C_SQLFILTER_WRITE => array('book' => array('title' => ' UPPER(?) ')))); $book = R::dispense('book'); $book->title = 'story'; R::store($book); asrt(R::getCell('SELECT title FROM book WHERE id = ?', array($book->id)), 'STORY'); $book = $book->fresh(); asrt($book->title, 'story'); $library = R::dispense('library'); $library->sharedBookList[] = $book; R::store($library); $library = $library->fresh(); $books = $library->sharedBookList; $book = reset($books); asrt($book->title, 'story'); $otherBook = R::dispense('book'); $otherBook->sharedBook[] = $book; R::store($otherBook); $otherBook = $otherBook->fresh(); $books = $otherBook->sharedBookList; $book = reset($books); asrt($book->title, 'story'); $links = $book->ownBookBookList; $link = reset($links); $link->shelf = 'x13'; AQueryWriter::setSQLFilters(array(QueryWriter::C_SQLFILTER_READ => array('book' => array('title' => ' LOWER(book.title) '), 'book_book' => array('shelf' => ' LOWER(book_book.shelf) ')), QueryWriter::C_SQLFILTER_WRITE => array('book' => array('title' => ' UPPER(?) '), 'book_book' => array('shelf' => ' UPPER(?) ')))); R::store($link); asrt(R::getCell('SELECT shelf FROM book_book WHERE id = ?', array($link->id)), 'X13'); $otherBook = $otherBook->fresh(); unset($book->sharedBookList[$otherBook->id]); R::store($book); AQueryWriter::setSQLFilters(array()); }
/** * Fills in a value of a binding and truncates the * resulting string if necessary. * * @param mixed $value bound value * * @return string */ protected function fillInValue($value) { if (is_null($value)) { $value = 'NULL'; } $value = strval($value); if (strlen($value) > $this->strLen) { $value = substr($value, 0, $this->strLen) . '... '; } if (!\RedBeanPHP\QueryWriter\AQueryWriter::canBeTreatedAsInt($value) && $value !== 'NULL') { $value = '\'' . $value . '\''; } return $value; }
/** * Test whether a duplicate bean in the list isnt saved. * This was an issue with Postgres while testing the threeway tables. * Postgres returned the ID as a string while other drivers returned * a numeric value causing different outcome in array_diff when * calculating the shared additions. * * @return void */ public function testIssueWithDriverReturnID() { AQueryWriter::clearRenames(); R::nuke(); $book = R::dispense('book'); $page = R::dispense('page'); $book->sharedPageList[] = $page; R::store($book); asrt(R::count('page'), 1); $book = $book->fresh(); $book->sharedPageList[] = $page; R::store($book); //don't save the duplicate bean! asrt(R::count('page'), 1); $book = $book->fresh(); $page->item = 2; //even if we change a property ? $book->sharedPageList[] = $page; R::store($book); foreach ($book->sharedPageList as $listItem) { asrt(is_string($listItem->id), TRUE); } //same test but for own-list R::nuke(); $book = R::dispense('book'); $page = R::dispense('page'); $book->ownPageList[] = $page; R::store($book); asrt(R::count('page'), 1); $book = $book->fresh(); $book->ownPageList[] = $page; R::store($book); //don't save the duplicate bean! asrt(R::count('page'), 1); $book = $book->fresh(); $book->ownPageList[] = $page; $page->item = 3; R::store($book); //don't save the duplicate bean! asrt(R::count('page'), 1); foreach ($book->ownPageList as $listItem) { asrt(is_string($listItem->id), TRUE); } AQueryWriter::clearRenames(); }
/** * Creates a N-M relation by linking an intermediate bean. * This method can be used to quickly connect beans using indirect * relations. For instance, given an album and a song you can connect the two * using a track with a number like this: * * Usage: * * $album->link('track', array('number'=>1))->song = $song; * * or: * * $album->link($trackBean)->song = $song; * * What this method does is adding the link bean to the own-list, in this case * ownTrack. If the first argument is a string and the second is an array or * a JSON string then the linking bean gets dispensed on-the-fly as seen in * example #1. After preparing the linking bean, the bean is returned thus * allowing the chained setter: ->song = $song. * * @param string|OODBBean $type type of bean to dispense or the full bean * @param string|array $qualification JSON string or array (optional) * * @return OODBBean */ public function link($typeOrBean, $qualification = array()) { if (is_string($typeOrBean)) { $typeOrBean = AQueryWriter::camelsSnake($typeOrBean); $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense($typeOrBean); if (is_string($qualification)) { $data = json_decode($qualification, TRUE); } else { $data = $qualification; } foreach ($data as $key => $value) { $bean->{$key} = $value; } } else { $bean = $typeOrBean; } $list = 'own' . ucfirst($bean->getMeta('type')); array_push($this->{$list}, $bean); return $bean; }
/** * Tests fix for issue #378. * Via property does not get cleared properly. * * @return void */ public function testIssue378() { R::nuke(); $mediaBean = R::dispense('media'); $fooPerson = R::dispense('person'); $mediaBean->sharedPersonList[] = $fooPerson; R::store($mediaBean); asrt(count($mediaBean->sharedPersonList), 1); $person = R::findOne('person'); $person->via('relation')->sharedMarriageList[] = R::dispense('marriage'); //this second one caused the via property to not get cleared $person->via('relation')->sharedMarriageList; asrt(count($person->sharedMediaList), 1); //also found this scenario, non-existing property $book = R::dispense('book'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->nothing; asrt(count($book->sharedPageList), 1); //yet another $book = R::dispense('magazine'); $book->ownAdList[] = R::dispense('ad'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->ownAdList; asrt(count($book->sharedPageList), 1); $book = R::dispense('folder'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->sharedItemList[] = R::dispense('item'); asrt(count($book->sharedPageList), 1); $book = R::dispense('folder2'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->sharedItemList[] = R::dispense('item'); $book->via('garbage')->sharedItemList[] = R::dispense('item'); asrt(count($book->sharedPageList), 1); $book = R::dispense('folder3'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->item = R::dispense('item'); asrt(count($book->sharedPageList), 1); $book = R::dispense('folder3'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->item = 'test'; asrt(count($book->sharedPageList), 1); //yet another $book = R::dispense('leaflet'); $book->title = 'leaflet'; $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->title; asrt(count($book->sharedPageList), 1); //yet another $book = R::dispense('paper'); $book->author = R::dispense('author'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->author; asrt(count($book->sharedPageList), 1); $book = R::dispense('paper2'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('garbage')->author; asrt(count($book->sharedPageList), 1); //yet another one $book = R::dispense('archive'); $book->sharedItem[] = R::dispense('item'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); unset($book->via('garbage')->sharedItem); asrt(count($book->sharedPageList), 1); //theoretic cases $book = R::dispense('guide'); $book->sharedItem[] = R::dispense('item'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('relation')->countShared('item'); $book->via('relation')->countShared('item'); asrt(count($book->sharedPageList), 1); $book = R::dispense('catalogue'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('relation')->countShared('item'); $book->via('relation')->countShared('item'); asrt(count($book->sharedPageList), 1); $book = R::dispense('tabloid'); $book->ownItemList[] = R::dispense('item'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('relation')->countOwn('item'); $book->via('relation')->countOwn('item'); asrt(count($book->sharedPageList), 1); $book = R::dispense('booklet'); $book->ownItemList[] = R::dispense('item'); $book->sharedPage[] = R::dispense('page'); R::store($book); $book = $book->fresh(); $book->via('relation')->countOwn('item'); $book->via('relation')->countOwn('item'); asrt(count($book->sharedPageList), 1); AQueryWriter::clearRenames(); }
/** * Facade method for AQueryWriter::renameAssociation() * * @param string|array $from * @param string $to * * @return void */ public static function renameAssociation($from, $to = NULL) { AQueryWriter::renameAssociation($from, $to); }
/** * Test prettier tables using via(). */ public function testViaPrettification() { R::nuke(); R::renameAssociation('tbl_author_tbl_friend', 'tbl_author_friend'); $author = R::xdispense(AUTHOR); $author->name = 'Mr. Quill'; $friend = R::xdispense(FRIEND); $friend->name = 'Muse'; $author->{FRIENDLIST}[] = $friend; $id = R::store($author); //print_r(R::inspect()); exit; $author = R::load(AUTHOR, $id); $tables = array_flip(R::inspect()); asrt(isset($tables['tbl_author_friend']), TRUE); asrt(isset($tables['tbl_author_tbl_friend']), FALSE); asrt(count($author->{FRIENDLIST}), 1); AQueryWriter::clearRenames(); }
/** * Binds parameters. This method binds parameters to a\PDOStatement for * Query Execution. This method binds parameters as NULL, INTEGER or STRING * and supports both named keys and question mark keys. * * @param \PDOStatement $statement \PDO Statement instance * @param array $bindings values that need to get bound to the statement * * @return void */ protected function bindParams($statement, $bindings) { foreach ($bindings as $key => &$value) { if (is_integer($key)) { if (is_null($value)) { $statement->bindValue($key + 1, NULL, \PDO::PARAM_NULL); } elseif (!$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt($value) && $value < 2147483648) { $statement->bindParam($key + 1, $value, \PDO::PARAM_INT); } else { $statement->bindParam($key + 1, $value, \PDO::PARAM_STR); } } else { if (is_null($value)) { $statement->bindValue($key, NULL, \PDO::PARAM_NULL); } elseif (!$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt($value) && $value < 2147483648) { $statement->bindParam($key, $value, \PDO::PARAM_INT); } else { $statement->bindParam($key, $value, \PDO::PARAM_STR); } } } }
/** * Determines whether the bea has a shared list based on * schema inspection from realtime schema or cache. * * @param string $type bean type to get list for * @param string $target type of list you are looking for * * @return boolean */ protected function hasSharedList($type, $target) { return in_array(AQueryWriter::getAssocTableFormat(array($type, $target)), $this->tables); }
/** * Test Facade bind function method. * Test for MySQL WKT spatial format. */ public function testFunctionFilters() { R::nuke(); R::bindFunc('read', 'location.point', 'asText'); R::bindFunc('write', 'location.point', 'GeomFromText'); R::store(R::dispense('location')); R::freeze(true); try { R::find('location'); fail(); } catch (SQL $exception) { pass(); } R::freeze(false); try { R::find('location'); pass(); } catch (SQL $exception) { fail(); } $location = R::dispense('location'); $location->point = 'POINT(14 6)'; R::store($location); $columns = R::inspect('location'); asrt($columns['point'], 'point'); $location = $location->fresh(); asrt($location->point, 'POINT(14 6)'); R::nuke(); $location = R::dispense('location'); $location->point = 'LINESTRING(0 0,1 1,2 2)'; R::store($location); $columns = R::inspect('location'); asrt($columns['point'], 'linestring'); $location->bustcache = 2; R::store($location); $location = $location->fresh(); asrt($location->point, 'LINESTRING(0 0,1 1,2 2)'); R::nuke(); $location = R::dispense('location'); $location->point = 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))'; R::store($location); $columns = R::inspect('location'); asrt($columns['point'], 'polygon'); $location->bustcache = 4; R::store($location); $location = $location->fresh(); asrt($location->point, 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))'); R::bindFunc('read', 'location.point', NULL); $location->bustcache = 1; R::store($location); $location = $location->fresh(); asrt($location->point === 'POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))', FALSE); $filters = AQueryWriter::getSQLFilters(); asrt(is_array($filters), TRUE); asrt(count($filters), 2); asrt(isset($filters[QueryWriter::C_SQLFILTER_READ]), TRUE); asrt(isset($filters[QueryWriter::C_SQLFILTER_WRITE]), TRUE); R::bindFunc('read', 'place.point', 'asText'); R::bindFunc('write', 'place.point', 'GeomFromText'); R::bindFunc('read', 'place.line', 'asText'); R::bindFunc('write', 'place.line', 'GeomFromText'); R::nuke(); $place = R::dispense('place'); $place->point = 'POINT(13.2 666.6)'; $place->line = 'LINESTRING(9.2 0,3 1.33)'; R::store($place); $columns = R::inspect('place'); asrt($columns['point'], 'point'); asrt($columns['line'], 'linestring'); $place = R::findOne('place'); asrt($place->point, 'POINT(13.2 666.6)'); asrt($place->line, 'LINESTRING(9.2 0,3 1.33)'); R::bindFunc('read', 'place.point', NULL); R::bindFunc('write', 'place.point', NULL); R::bindFunc('read', 'place.line', NULL); R::bindFunc('write', 'place.line', NULL); }
/** * Binds an SQL function to a column. * This method can be used to setup a decode/encode scheme or * perform UUID insertion. This method is especially useful for handling * MySQL spatial columns, because they need to be processed first using * the asText/GeomFromText functions. * * @param string $mode mode to set function for, i.e. read or write * @param string $field field (table.column) to bind SQL function to * @param string $function SQL function to bind to field * * @return void */ public function bindFunc($mode, $field, $function) { list($type, $property) = explode('.', $field); $mode = $mode === 'write' ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ; if (!isset(self::$sqlFilters[$mode])) { self::$sqlFilters[$mode] = array(); } if (!isset(self::$sqlFilters[$mode][$type])) { self::$sqlFilters[$mode][$type] = array(); } if (is_null($function)) { unset(self::$sqlFilters[$mode][$type][$property]); } else { if ($mode === QueryWriter::C_SQLFILTER_WRITE) { self::$sqlFilters[$mode][$type][$property] = $function . '(?)'; } else { self::$sqlFilters[$mode][$type][$property] = $function . "({$field})"; } } AQueryWriter::setSQLFilters(self::$sqlFilters, !$this->isFrozen); }
/** * Counts the number of beans of type $type. * This method accepts a second argument to modify the count-query. * A third argument can be used to provide bindings for the SQL snippet. * * @param string $type type of bean we are looking for * @param string $addSQL additional SQL snippet * @param array $bindings parameters to bind to SQL * * @return integer * * @throws SQLException */ public function count($type, $addSQL = '', $bindings = array()) { $type = AQueryWriter::camelsSnake($type); if (count(explode('_', $type)) > 2) { throw new RedException('Invalid type for count.'); } try { return (int) $this->writer->queryRecordCount($type, array(), $addSQL, $bindings); } catch (SQLException $exception) { if (!$this->writer->sqlStateIn($exception->getSQLState(), array(QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN))) { throw $exception; } } return 0; }
/** * @see QueryWriter::esc */ public function esc($dbStructure, $noQuotes = FALSE) { return parent::esc(strtolower($dbStructure), $noQuotes); }
/** * Test camelCase to snake_case conversions. * * @return void */ public function testCamel2Snake() { asrt(AQueryWriter::camelsSnake('bookPage'), 'book_page'); asrt(AQueryWriter::camelsSnake('FTP'), 'ftp'); asrt(AQueryWriter::camelsSnake('ACLRules'), 'acl_rules'); asrt(AQueryWriter::camelsSnake('SSHConnectionProxy'), 'ssh_connection_proxy'); asrt(AQueryWriter::camelsSnake('proxyServerFacade'), 'proxy_server_facade'); asrt(AQueryWriter::camelsSnake('proxySSHClient'), 'proxy_ssh_client'); asrt(AQueryWriter::camelsSnake('objectACL2Factory'), 'object_acl2_factory'); asrt(AQueryWriter::camelsSnake('bookItems4Page'), 'book_items4_page'); asrt(AQueryWriter::camelsSnake('book☀Items4Page'), 'book☀_items4_page'); }
/** * @see QueryWriter::scanType */ public function scanType($value, $flagSpecial = FALSE) { $this->svalue = $value; if ($value === INF) { return self::C_DATATYPE_TEXT; } if ($flagSpecial && $value) { if (preg_match('/^\\d{4}\\-\\d\\d-\\d\\d$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_DATE; } if (preg_match('/^\\d{4}\\-\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d(\\.\\d{1,6})?$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME; } if (preg_match('/^\\([\\d\\.]+,[\\d\\.]+\\)$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_POINT; } if (preg_match('/^\\[\\([\\d\\.]+,[\\d\\.]+\\),\\([\\d\\.]+,[\\d\\.]+\\)\\]$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_LSEG; } if (preg_match('/^\\<\\([\\d\\.]+,[\\d\\.]+\\),[\\d\\.]+\\>$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE; } if (preg_match('/^\\((\\([\\d\\.]+,[\\d\\.]+\\),?)+\\)$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON; } if (preg_match('/^\\-?(\\$|€|¥|£)[\\d,\\.]+$/', $value)) { return PostgreSQL::C_DATATYPE_SPECIAL_MONEY; } } if (is_float($value)) { return self::C_DATATYPE_DOUBLE; } if ($this->startsWithZeros($value)) { return self::C_DATATYPE_TEXT; } if ($value === FALSE || $value === TRUE || $value === NULL || is_numeric($value) && AQueryWriter::canBeTreatedAsInt($value) && $value < 2147483648.0 && $value > -2147483648.0) { return self::C_DATATYPE_INTEGER; } elseif (is_numeric($value)) { return self::C_DATATYPE_DOUBLE; } else { return self::C_DATATYPE_TEXT; } }
/** * Registers a association renaming globally. * * @param string $via type you wish to use for shared lists * * @return OODBBean */ public function via($via) { $this->via = AQueryWriter::camelsSnake($via); return $this; }
/** * Test whether unique constraints are properly created using * reflection. * * @return void */ public function testUniqueInspect() { $writer = R::getWriter(); R::nuke(); $book = R::dispense('book'); $category = R::dispense('category'); $book->sharedCategory[] = $category; R::store($book); asrt(count(get_uniques_for_type('book_category')), 1); asrt(are_cols_in_unique('book_category', array('book_id', 'category_id')), TRUE); R::nuke(); $book = R::dispense('book'); $category = R::dispense('category'); $book->via('library')->sharedCategory[] = $category; R::store($book); asrt(count(get_uniques_for_type('book_category')), 0); asrt(are_cols_in_unique('book_category', array('book_id', 'category_id')), FALSE); asrt(count(get_uniques_for_type('library')), 1); asrt(are_cols_in_unique('library', array('book_id', 'category_id')), TRUE); AQueryWriter::clearRenames(); R::nuke(); $book = R::dispense('book'); $category = R::dispense('category'); $book->sharedCategory[] = $category; R::store($book); asrt(count(get_uniques_for_type('book_category')), 1); asrt(are_cols_in_unique('book_category', array('book_id', 'category_id')), TRUE); asrt(count(get_uniques_for_type('library')), 0); asrt(are_cols_in_unique('library', array('book_id', 'category_id')), FALSE); R::nuke(); $book = R::dispense('book'); $book2 = R::dispense('book'); $book->sharedBook[] = $book2; R::store($book); asrt(count(get_uniques_for_type('book_book')), 1); asrt(are_cols_in_unique('book_book', array('book_id', 'book2_id')), TRUE); try { $result = R::getWriter()->addUniqueConstraint('nonexistant', array('a', 'b')); } catch (\Exception $e) { print_r($e); exit; } pass(); //dont crash! asrt($result, FALSE); }