How to clean up referenced files after a cascaded delete in an RDMS? - zend-db

Zend_Db contains methods to cascade deletes and updates much like the way that most RDMS's will. The documentation states that if you are using a RDMS which supports forign keys that you should use that in place of the support in Zend_Db. The reasons for this are obvious; Zend_Db issues separate db queries for each delete/update so you lose both performance and (depending on transactional concurrency settings) atomicity.
What then is the best option given the following scenario:
A user has multiple albums, an album contains multiple photos. Each photo, album and user has a single row in equivilent db tables, however each photo is also stored on a CDN. If a user is deleted, the database cascade deletes their albums and photos. However the photos remain on the CDN and need to be removed.

maybe it would be possible to overwrite default cascade delete method with my own model's delete method but it souds like a lot of work so i've made a workaround for my project.
i have 'zadania' table and 'zadania_images' table. the second table has 'zadanie_id' column which relates the two tables.
'zadanie_images' rows contain filenames and after cascade delete of parent 'zadanie_id' the files were still there. here's my solution:
in 'Zadanie' model:
public function deleteZadanie($id) {
$row = $this->find($id)->current();
if($row) {
$image = new Model_ZadanieImage();
$image->deleteRelatedFiles($id);
$row->delete();
}
}
in 'ZadanieImage' model:
public function deleteImage($id) {
$row = $this->find($id)->current();
if($row) {
//delete the record
$filename = $row['name'];
$row->delete();
//and delete related files
unlink('images/zadanie/' . $filename);
unlink('images/zadanie/thumbs/' . $filename);
}
}
public function deleteRelatedFiles($zadanie_id) {
$select = $this->select()->where('zadanie=?', $zadanie_id);
$images = $this->fetchAll($select);
foreach($images as $image) {
$this->deleteImage($image->id);
}
}

Related

Best way to delete Firestore document that you are unsure if it exists?

I'm using Firestore as the NoSQL database to build an app that needs to let users add as friends, block, ... other users
To block someone, I'll set the blocked values, and then delete the current friendship status (if any), but I find this a bit tricky. Should I first check if the document exists, and just then delete it, or does Firestore achieve this automatically? Would I be wasting time & Firestore operations if I add the extra checks?
fbRef.runBatch {
it.delete(userFriendsWith)
it.delete(blockedUserFriendsWith)
...
}
fbRef.runBatch {
it.get() {
...
if (document.exists()) {
it.delete(userFriendsWith)
}
}
}
Thanks!
Just delete the document. There's no need to read it first, if you don't care what's inside. The delete operation won't fail if the document already doesn't exist.

Error while creating foreign relation in knex - table creation race condition

I'm new to knex and haven't touched RDBMS in years (been in NoSQL land), so bear with me here.
I've got two migration files, one for tracks and one for users (tracks are owned by users). Below are the relevant files:
migrations/20190919103115_users.js
exports.up = function(knex) {
return knex.schema.createTable('users', table => {
table.increments('id');
table.string('email', 50);
table.string('first_name', 50);
table.string('last_name', 50);
}
};
exports.down = function(knex) {
return knex.schema.dropTable('users');
};
migrations/20190406112728_tracks.js
exports.up = function(knex) {
return knex.schema.createTable('tracks', table => {
table.increments('id');
table.string('name', 140).notNullable();
table.integer('owner_id').notNullable();
table
.foreign('owner_id')
.references('id')
.inTable('users')
.onDelete('CASCADE');
table.json('metadata');
});
};
exports.down = function(knex) {
return knex.schema.dropTable('tracks');
};
When I run yarn knex migrate:up, I get:
migration file "20190406112728_tracks.js" failed
migration failed with error: alter table "tracks" add constraint "tracks_owner_id_foreign" foreign key ("owner_id") references "users" ("id") on delete CASCADE - relation "users" does not exist
I find the official Knex documentation to be pretty lacking (it's more of a reference than anything else) and can't figure out what I'm missing. Obviously I need some way for users to be created before tracks, but don't know how.
EDIT:
It seems this is how it's done: https://github.com/tgriesser/knex/issues/938#issuecomment-131491877
But it seems wrong to just put the entire set of tables in a single migration file. I thought the point was to create one migration file per table?
Migration files are sorted by name before execution, so looks like your tracks file name has an earlier date, therefore it runs before creation of users.
just run npx knex migrate:make create_users, and then npx knex migrate:make create_tracks.
it will generate new files with the proper timestamp, copy your code to the new files, delete the old ones :]

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

Sitefinity - Safely delete orphaned dynamic content records

I've been adding records to a dynamic module via the API and in the process during my experimentation I added a bunch of records that weren't associated correctly with any valid parent record.
I've checked and so far I can see that Sitefinity stores data about these records in a number of tables:
mydynamiccontenttype_table
sf_dynamic_content
sf_dynmc_cntnt_sf_lnguage_data
sf_dynmc_cntent_sf_permissions
I would like to clean up the database by deleting these records but I want to make sure I don't create more problems in the process.
Anyone know if there are more references to these dynamic content type records or a process to safely delete them?
There are probably other tables, so your safest option would be to delete the items using the Sitefinity API.
Just get the masterId of the item and use a code like this:
public static void DeleteDataItemOfType(this DynamicModuleManager manager, string type, Guid Id)
{
Type resolvedType = TypeResolutionService.ResolveType(type);
using (var region = new ElevatedModeRegion(manager))
{
manager.DeleteDataItem(resolvedType, Id);
manager.SaveChanges();
}
}

Raven db: creating a new database

I am new to raven db. I have read the API and try to create a database . It has something like EnsureDatabaseExists function which creates the database if it does not exist. It actually uses DocumentDatabase type to create that database. I use it and it creates the database but I want to use this object directly so that using this object I can directly work with documents. Am i doing right? Or can there be any better approach then this to work with documents.Thank you.
I think you're confusing the database document and querying documents.
The database document is a document on the default database, which just represent a database in RavenDB which is not the default database. It stores some data like the database name and location. You, as a consumer of ravendb as nothing to do with this document. And this has nothing to do with querying any other documents.
Look here in order to learn how to query ravendb for documents. In order to query a specific database, if you work just with that database than you better just specify the database name in the connection string. If you work against multiy databases at once, you can specify the database name that you want when you open a session, store.OpenSession("database-name").
Three methods are available on store.DatabaseCommands.GlobalAdmin.
GetDatabaseNames: lists database names
EnsureDatabaseExists: creates database if it does not exists
CreateDatabase: creates database
Note that DocumentStore.Initialize() already ensures that the database is created. You can pass a boolean false to avoid this behavior.
// init store object, you pass the service URL + the database name
var store = new DocumentStore("http://localhost:8001/databases/MyNewDataBase");
store.Initialize(false);
// most simple thing is:
var dbName = store.DefaultDatabase;
store.DatabaseCommands.GlobalAdmin.EnsureDatabaseExists(dbName);
If you want to check without creating:
// there is a method to list the database names
bool exists = false;
for (int i = 0; i < int.MaxValue; i++)
{
var names = store.DatabaseCommands.GlobalAdmin.GetDatabaseNames(100, i * 100);
if (names.Contains(dbName))
{
exists = true;
break;
}
if (names.Length < 100)
{
// no more databases
break;
}
}
if (exists)
{
// database exists, do something
}
Reference: https://ravendb.net/docs/article-page/3.5/Csharp/client-api/commands/how-to/create-delete-database