535 lines
13 KiB
PHP
535 lines
13 KiB
PHP
<?php
|
|
|
|
namespace alveolata\storage;
|
|
|
|
// require_once(DIR_ALVEOLATA . '/definitions.php');
|
|
require_once(DIR_ALVEOLATA . '/map/functions.php');
|
|
require_once(DIR_ALVEOLATA . '/observer/wrapper-class.php');
|
|
require_once(DIR_ALVEOLATA . '/list/functions.php');
|
|
require_once(DIR_ALVEOLATA . '/storage/implementation-sqltable/functions.php');
|
|
|
|
|
|
/**
|
|
* exemplary structure:
|
|
* »CREATE TABLE core(id);«
|
|
* »CREATE TABLE lump(id, foo_id);«
|
|
*
|
|
* typical use case: for representing values with a type like "record<foo:string,bar:list<integer>>" in a relational
|
|
* database, one would create a core table for the primitive record fields (foo) and separate tables for all
|
|
* non-primitive fields (bar), which are linked to the core table
|
|
*
|
|
* @template type_value
|
|
* @template type_element
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
class struct_sqltablecluster_tight_supplement
|
|
{
|
|
|
|
/**
|
|
* @var struct_sqltable
|
|
*/
|
|
public $target_table;
|
|
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
public $core_id_column;
|
|
|
|
|
|
/**
|
|
* @var boolean
|
|
*/
|
|
public $include_own_id;
|
|
|
|
|
|
/**
|
|
* @var boolean
|
|
*/
|
|
public $exclude_core_id;
|
|
|
|
|
|
/**
|
|
*/
|
|
public function __construct(
|
|
struct_sqltable $target_table,
|
|
string $core_id_column,
|
|
bool $include_own_id,
|
|
bool $exclude_core_id
|
|
)
|
|
{
|
|
$this->target_table = $target_table;
|
|
$this->core_id_column = $core_id_column;
|
|
$this->include_own_id = $include_own_id;
|
|
$this->exclude_core_id = $exclude_core_id;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* exemplary structure:
|
|
* »CREATE TABLE core(id);«
|
|
* »CREATE TABLE target(id);«
|
|
* »CREATE TABLE edge(core_id,target_id);«
|
|
*
|
|
* typical use case: domain B depends on domain A; elements of A can exist in ignorance of B
|
|
*
|
|
*
|
|
* @template type_value
|
|
* @template type_thing
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
class struct_sqltablecluster_loose_supplement
|
|
{
|
|
|
|
/**
|
|
* @var struct_sqltable
|
|
*
|
|
* will only be used to add a deletion hook
|
|
*/
|
|
public $target_table;
|
|
|
|
|
|
/**
|
|
* @var struct_sqltable
|
|
*/
|
|
public $edge_table;
|
|
|
|
|
|
/**
|
|
* @var string
|
|
*
|
|
* name of the column in the edge table, which references the id in the core table
|
|
*/
|
|
public $core_id_column;
|
|
|
|
|
|
/**
|
|
* @var string
|
|
*
|
|
* name of the column in the edge table, which references the id in the target table
|
|
*/
|
|
public $target_id_column;
|
|
|
|
|
|
/**
|
|
*/
|
|
public function __construct(
|
|
struct_sqltable $target_table,
|
|
struct_sqltable $edge_table,
|
|
string $core_id_column,
|
|
string $target_id_column
|
|
)
|
|
{
|
|
$this->target_table = $target_table;
|
|
$this->edge_table = $edge_table;
|
|
$this->core_id_column = $core_id_column;
|
|
$this->target_id_column = $target_id_column;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
class struct_sqltablecluster
|
|
{
|
|
|
|
/**
|
|
* @var struct_sqltable
|
|
*/
|
|
public $core;
|
|
|
|
|
|
/**
|
|
* @var array {map<string,struct_sqltablecluster_tight_supplement<type_value,any>>}
|
|
*/
|
|
public $tight_supplements;
|
|
|
|
|
|
/**
|
|
* @var array {map<string,struct_sqltablecluster_loose_supplement<type_value,any>>}
|
|
*/
|
|
public $loose_supplements;
|
|
|
|
|
|
/**
|
|
* @var \Closure {
|
|
* function<
|
|
* record<
|
|
* core_row:map<string,any>,
|
|
* tight_supplement_values:map<string,list<any>>,
|
|
* loose_supplement_values:map<string,list<integer>>
|
|
* >,
|
|
* type_value
|
|
* >
|
|
* }
|
|
*/
|
|
public $assemble;
|
|
|
|
|
|
/**
|
|
* @var \Closure {
|
|
* function<
|
|
* type_value,
|
|
* record<
|
|
* core_row:map<string,any>,
|
|
* tight_supplement_values:map<string,list<any>>,
|
|
* loose_supplement_values:map<string,list<integer>>
|
|
* >
|
|
* >
|
|
* }
|
|
*/
|
|
public $disperse;
|
|
|
|
|
|
/**
|
|
* @var \alveolata\observer\class_observer
|
|
*/
|
|
public $observer_delete;
|
|
|
|
|
|
/**
|
|
*/
|
|
public function __construct(
|
|
struct_sqltable $core,
|
|
array $tight_supplements,
|
|
array $loose_supplements,
|
|
\Closure $assemble,
|
|
\Closure $disperse
|
|
)
|
|
{
|
|
$this->core = $core;
|
|
$this->tight_supplements = $tight_supplements;
|
|
$this->loose_supplements = $loose_supplements;
|
|
$this->assemble = $assemble;
|
|
$this->disperse = $disperse;
|
|
|
|
$this->observer_delete = \alveolata\observer\class_observer::make();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function sqltablecluster_make(
|
|
struct_sqltable $core,
|
|
array $tight_supplements,
|
|
array $loose_supplements,
|
|
\Closure $assemble,
|
|
\Closure $disperse
|
|
) : struct_sqltablecluster
|
|
{
|
|
$subject = (
|
|
new struct_sqltablecluster(
|
|
$core,
|
|
$tight_supplements,
|
|
$loose_supplements,
|
|
$assemble,
|
|
$disperse
|
|
)
|
|
);
|
|
// hooks
|
|
{
|
|
// remove corresponding edges, when a supplemental dataset is deleted
|
|
{
|
|
foreach ($subject->loose_supplements as $loose_supplement_name => $loose_supplement) {
|
|
sqltable_hook_delete(
|
|
$loose_supplement->target_table,
|
|
function ($target_id) use ($loose_supplement) : void {
|
|
$loose_supplement_ids = sqltable_search(
|
|
$loose_supplement->edge_table,
|
|
[$loose_supplement->target_id_column => $target_id]
|
|
);
|
|
\alveolata\list_\map/*<int,void>*/(
|
|
$loose_supplement_ids,
|
|
function (int $loose_supplement_id) use ($loose_supplement) : void {
|
|
sqltable_delete($loose_supplement->edge_table, $loose_supplement_id);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return $subject;
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function sqltablecluster_hook_delete(
|
|
struct_sqltable $subject,
|
|
\Closure $procedure
|
|
) : string
|
|
{
|
|
return $subject->observer_delete->register($procedure);
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function sqltablecluster_create(
|
|
struct_sqltablecluster $subject,
|
|
/*map<string,any> */$value
|
|
)/* : int*/
|
|
{
|
|
$dispersal = ($subject->disperse)($value);
|
|
$core_row = $dispersal['core_row'];
|
|
// core
|
|
{
|
|
$core_id = sqltable_create($subject->core, $core_row);
|
|
}
|
|
// tight supplements
|
|
{
|
|
$tight_supplement_ids = \alveolata\map\map/*<any,int>*/(
|
|
$subject->tight_supplements,
|
|
function ($tight_supplement, $tight_supplement_name) use ($dispersal, $core_id) {
|
|
return \alveolata\list_\map(
|
|
$dispersal['tight_supplement_values'][$tight_supplement_name],
|
|
function ($tight_supplement_row) use ($core_id, $tight_supplement) {
|
|
$tight_supplement_row[$tight_supplement->core_id_column] = $core_id;
|
|
return sqltable_create($tight_supplement->target_table, $tight_supplement_row);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
// loose supplements
|
|
{
|
|
$loose_supplement_ids = \alveolata\map\map/*<any,int>*/(
|
|
$subject->loose_supplements,
|
|
function ($loose_supplement, $loose_supplement_name) use ($dispersal, $core_id) {
|
|
return \alveolata\list_\map(
|
|
$dispersal['loose_supplement_values'][$loose_supplement_name],
|
|
function (int $target_id) use ($core_id, $loose_supplement) {
|
|
$loose_supplement_row = [
|
|
$loose_supplement->core_id_column => $core_id,
|
|
$loose_supplement->target_id_column => $target_id,
|
|
];
|
|
return sqltable_create($loose_supplement->edge_table, $loose_supplement_row);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
$key = $core_id;
|
|
return $key;
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
* @todo improve update of satellites
|
|
*/
|
|
function sqltablecluster_update(
|
|
struct_sqltablecluster $subject,
|
|
/*int */$key,
|
|
/*map<string,any> */$value
|
|
) : void
|
|
{
|
|
$core_id = $key;
|
|
$dispersal = ($subject->disperse)($value);
|
|
$core_row = $dispersal['core_row'];
|
|
// core
|
|
{
|
|
sqltale_update($subject->core, $core_id, $core_row);
|
|
}
|
|
// tight supplements
|
|
{
|
|
\alveolata\map\map(
|
|
$subject->tight_supplements,
|
|
function ($tight_supplement, $tight_supplement_name) use ($core_id, $value, $dispersal) {
|
|
// remove old
|
|
{
|
|
$tight_supplement_ids_old = sqltable_search($tight_supplement->target_table, [$tight_supplement->core_id_column => $core_id]);
|
|
\alveolata\list_\map/*<int,void>*/(
|
|
$tight_supplement_ids_old,
|
|
function (int $tight_supplement_id_old) use ($tight_supplement) : void {
|
|
sqltable_delete($tight_supplement->target_table, $tight_supplement_id_old);
|
|
}
|
|
);
|
|
}
|
|
// insert new
|
|
{
|
|
$tight_supplement_ids_new = \alveolata\list_\map(
|
|
$dispersal['tight_supplement_values'][$tight_supplement_name],
|
|
function ($tight_supplement_row) use ($core_id, $tight_supplement) {
|
|
$tight_supplement_row[$tight_supplement->core_id_column] = $core_id;
|
|
return sqltable_create($tight_supplement->target_table, $tight_supplement_row);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
// loose supplements
|
|
{
|
|
\alveolata\map\map(
|
|
$subject->loose_supplements,
|
|
function ($loose_supplement, $loose_supplement_name) use ($core_id, $value, $dispersal) {
|
|
// remove old
|
|
{
|
|
$loose_supplement_ids_old = sqltable_search($loose_supplement->edge_table, [$loose_supplement->core_id_column => $core_id]);
|
|
\alveolata\list_\map/*<int,void>*/(
|
|
$loose_supplement_ids_old,
|
|
function (int $loose_supplement_id_old) use ($loose_supplement) : void {
|
|
sqltable_delete($loose_supplement->edge_table, $loose_supplement_id_old);
|
|
}
|
|
);
|
|
}
|
|
// insert new
|
|
{
|
|
$loose_supplement_ids_new = \alveolata\list_\map(
|
|
$dispersal['loose_supplement_values'][$loose_supplement_name],
|
|
function (int $target_id) use ($core_id, $loose_supplement) {
|
|
$loose_supplement_row = [
|
|
$loose_supplement->core_id_column => $core_id,
|
|
$loose_supplement->target_id_column => $target_id,
|
|
];
|
|
return sqltable_create($loose_supplement->edge_table, $loose_supplement_row);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function sqltablecluster_delete(
|
|
struct_sqltablecluster $subject,
|
|
/*int */$key
|
|
) : void
|
|
{
|
|
// delete depedent data
|
|
{
|
|
$observation = $subject->observer_delete->notify($key);
|
|
}
|
|
$core_id = $key;
|
|
$core_row = sqltable_read($subject->core, $core_id);
|
|
// loose_supplements
|
|
{
|
|
\alveolata\map\map(
|
|
$subject->loose_supplements,
|
|
function (struct_sqltablecluster_loose_supplement $loose_supplement) use ($core_id) {
|
|
$loose_supplement_ids = sqltable_search($loose_supplement->edge_table, [$loose_supplement->core_id_column => $core_id]);
|
|
return \alveolata\list_\map/*<int,void>*/(
|
|
$loose_supplement_ids,
|
|
function (int $loose_supplement_id) use ($loose_supplement) : void {
|
|
sqltable_delete($loose_supplement->edge_table, $loose_supplement_id);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
// tight_supplements
|
|
{
|
|
\alveolata\map\map(
|
|
$subject->tight_supplements,
|
|
function (struct_sqltablecluster_tight_supplement $tight_supplement) use ($core_id) {
|
|
$tight_supplement_ids = sqltable_search($tight_supplement->target_table, [$tight_supplement->core_id_column => $core_id]);
|
|
return \alveolata\list_\map/*<int,void>*/(
|
|
$tight_supplement_ids,
|
|
function (int $tight_supplement_id) use ($tight_supplement) : void {
|
|
sqltable_delete($tight_supplement->target_table, $tight_supplement_id);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
// core
|
|
{
|
|
sqltable_delete($subject->core, $core_id);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function sqltablecluster_read(
|
|
struct_sqltablecluster $subject,
|
|
/*int */$key
|
|
)/* : map<string,any>*/
|
|
{
|
|
$core_id = $key;
|
|
// core
|
|
{
|
|
$core_row = sqltable_read($subject->core, $core_id);
|
|
}
|
|
// tight supplements
|
|
{
|
|
$tight_supplement_values = \alveolata\map\map(
|
|
$subject->tight_supplements,
|
|
function (struct_sqltablecluster_tight_supplement $tight_supplement) use ($core_id) {
|
|
$tight_supplement_ids = sqltable_search($tight_supplement->target_table, [$tight_supplement->core_id_column => $core_id]);
|
|
sort($tight_supplement_ids);
|
|
$value = \alveolata\list_\map/*<int,string>*/(
|
|
$tight_supplement_ids,
|
|
function (int $tight_supplement_id) use ($tight_supplement) {
|
|
$tight_supplement_row = sqltable_read($tight_supplement->target_table, $tight_supplement_id);
|
|
if ($tight_supplement->include_own_id) {
|
|
$tight_supplement_row['id'] = $tight_supplement_id;
|
|
}
|
|
if ($tight_supplement->exclude_core_id) {
|
|
unset($tight_supplement_row[$tight_supplement->core_id_column]);
|
|
}
|
|
return $tight_supplement_row;
|
|
}
|
|
);
|
|
return $value;
|
|
}
|
|
);
|
|
}
|
|
// loose supplements
|
|
{
|
|
$loose_supplement_values = \alveolata\map\map(
|
|
$subject->loose_supplements,
|
|
function (struct_sqltablecluster_loose_supplement $loose_supplement) use ($core_id) {
|
|
$loose_supplement_ids = sqltable_search($loose_supplement->edge_table, [$loose_supplement->core_id_column => $core_id]);
|
|
sort($loose_supplement_ids);
|
|
$value = \alveolata\list_\map/*<int,string>*/(
|
|
$loose_supplement_ids,
|
|
function (int $loose_supplement_id) use ($loose_supplement) {
|
|
$loose_supplement_row = sqltable_read($loose_supplement->edge_table, $loose_supplement_id);
|
|
$target_id = $loose_supplement_row[$loose_supplement->target_id_column];
|
|
return $target_id;
|
|
}
|
|
);
|
|
return $value;
|
|
}
|
|
);
|
|
}
|
|
$stuff = [
|
|
'core_row' => $core_row,
|
|
'tight_supplement_values' => $tight_supplement_values,
|
|
'loose_supplement_values' => $loose_supplement_values,
|
|
];
|
|
$value = ($subject->assemble)($stuff);
|
|
return $value;
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
* @todo search in satellites
|
|
*/
|
|
function sqltablecluster_search(
|
|
struct_sqltablecluster $subject,
|
|
array $parameters = []
|
|
) : array
|
|
{
|
|
return sqltable_search($subject->core, $parameters);
|
|
}
|
|
|
|
?>
|