/
toba_ap_tabla_db.php
1420 lines (1306 loc) · 52.9 KB
/
toba_ap_tabla_db.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
define("apex_db_registros_separador","%");
/**
* Administrador de persistencia a una tabla de DB desde un @see toba_datos_tabla
* Supone que la tabla de datos se va a mapear a algun tipo de estructura en una base de datos
*
* TODO Poder desactivar el control de sincronizacion (¿se necesita esto?)
* TODO Como se implementa la carga de columnas externas??
* TODO Donde se hacen los controles pre-sincronizacion (nulos db)??
* TODO Hay que definir el manejo de claves (en base a toba_datos_relacion)
*
* @package Componentes\Persistencia
*/
abstract class toba_ap_tabla_db implements toba_ap_tabla
{
const tipo_tabla_unica = 'st';
const tipo_multitabla = 'mt';
/**
* @var toba_datos_tabla
*/
protected $objeto_tabla; // DATOS_TABLA: Referencia al objeto asociado
protected $_columnas; // DATOS_TABLA: Estructura del objeto
protected $datos; // DATOS_TABLA: DATOS que conforman las filas
protected $_cambios; // DATOS_TABLA: Estado de los cambios
protected $_tabla; // DATOS_TABLA: Tabla
protected $_alias; // DATOS_TABLA: Alias
protected $_clave; // DATOS_TABLA: Clave
protected $_fuente; // DATOS_TABLA: Fuente de datos
protected $_secuencias = array();
protected $_columnas_predeterminadas_db; // Manejo de datos generados por el motor (autonumericos, predeterninados, etc)
protected $_sql_carga; // Partes de la SQL utilizado en la carga de la tabla
protected $_schema;
//-------------------------------
protected $_baja_logica = false; // Baja logica. (delete = update de una columna a un valor)
protected $_baja_logica_columna; // Columna de la baja logica
protected $_baja_logica_valor; // Valor de la baja logica
protected $_flag_modificacion_clave = false; // Es posible modificar la clave en el UPDATE? Por defecto
protected $_proceso_carga_externa = array(); // Declaracion del proceso utilizado para cargar columnas externas
//-------------------------------
protected $_control_sincro_db; // Se activa el control de sincronizacion con la DB?
protected $_utilizar_transaccion=true; // La sincronizacion con la DB se ejecuta dentro de una transaccion
protected $_msg_error_sincro = "Error interno. Los datos no fueron guardados.";
protected $_hacer_trim_datos = true; // Hace un trim de los datos en el insert/update
protected $_lock_optimista = true;
//-------------------------------
protected $_insert_campos_default = array();
protected $_usar_perfil_de_datos = false;
/**
* @param toba_datos_tabla $datos_tabla Tabla que persiste
*/
function __construct($datos_tabla)
{
$this->objeto_tabla = $datos_tabla;
$this->_tabla = $this->objeto_tabla->get_tabla();
$this->_alias = $this->objeto_tabla->get_alias();
$this->_clave = $this->objeto_tabla->get_clave();
$this->_columnas = $this->objeto_tabla->get_columnas();
$this->_fuente = $this->objeto_tabla->get_fuente();
$this->_schema = $this->objeto_tabla->get_schema();
//Determino las secuencias de la tabla
foreach($this->_columnas as $columna){
if( $columna['secuencia']!=""){
$this->_secuencias[$columna['columna']] = $columna['secuencia'];
}
}
}
/**
* Ventana para agregar configuraciones particulares antes de que el objeto sea construido en su totalidad
* @deprecated
* @see ini
* @ventana
*/
protected function inicializar(){}
/**
* Ventana para agregar configuraciones particulares despues de la construccion
* @ventana
*/
protected function ini(){}
/**
* @ignore
*/
protected function get_estado_datos_tabla()
{
$this->_cambios = $this->objeto_tabla->get_cambios();
$this->datos = $this->objeto_tabla->get_conjunto_datos_interno();
}
/**
* Shorcut a toba::logger()->debug incluyendo infomación básica del componente
*/
protected function log($txt)
{
toba::logger()->debug("AP_TABLA: [{$this->_tabla}]\n".$txt, 'toba');
}
/**
* Método de debug que retorna las propiedades internas
* @return array
*/
function info()
{
return get_object_vars($this);
}
//-------------------------------------------------------------------------------
//------ Configuracion --------------------------------------------------------
//-------------------------------------------------------------------------------
/**
* Activa el uso de perfil de datos en la carga del componente
*/
function activar_perfil_de_datos()
{
$this->_usar_perfil_de_datos = true;
}
/**
* Utilizar una transaccion de BD cuando sincroniza la tabla
*/
function activar_transaccion()
{
$this->_utilizar_transaccion = true;
}
/**
* No utilizar una transaccion de BD cuando sincroniza la tabla
* Generalmente por que la transaccion la abre/cierra algun proceso de nivel superior
*/
function desactivar_transaccion()
{
$this->_utilizar_transaccion = false;
}
/**
* Carga una columna separada del proceso común de carga
* Se brinda una query que carga una o más columnas denominadas como 'externas'
* Una columna externa no participa en la sincronización posterior, pero por necesidades casi siempre estéticas
* necesitan mantenerse junto al conjunto de datos.
*
* @param string $sql Query de carga que devolvera un registro conteniendo las columnas 'externas'
* @param array $col_parametros Columnas que espera recibir el sql, en la sql necesitan esta el campo entre % (%nombre_campo%)
* @param array $col_resultado Columnas del registro resultante que se tomarán para rellenar la tabla
* @param boolean $sincro_continua En cada pedido de página ejecuta la sql para actualizar los valores de las columnas
* @param boolean $estricto Indica si es imperioso que la columna externa posea un estado o se
* permite que no posea valor.
*/
function activar_proceso_carga_externa_sql($sql, $col_parametros, $col_resultado, $sincro_continua=true, $estricto = true)
{
$proximo = count($this->_proceso_carga_externa);
$this->_proceso_carga_externa[$proximo]["tipo"] = "sql";
$this->_proceso_carga_externa[$proximo]["sql"] = $sql;
$this->_proceso_carga_externa[$proximo]["col_parametro"] = $col_parametros;
$this->_proceso_carga_externa[$proximo]["col_resultado"] = $col_resultado;
$this->_proceso_carga_externa[$proximo]["sincro_continua"] = $sincro_continua;
$this->_proceso_carga_externa[$proximo]["dato_estricto"] = $estricto;
}
/**
* Carga una columna separada del proceso común de carga
* Se brinda un DAO que carga una o más columnas denominadas como 'externas'
* Una columna externa no participa en la sincronización posterior, pero por necesidades casi siempre estéticas
* necesitan mantenerse junto al conjunto de datos.
*
* @param string $método Método que obtiene los datos.
* @param string $clase Clase a la que pertenece el método. Si es NULL usa el mismo AP
* @param string $include Archivo donde se encuentra la clase. Si es NULL usa el mismo AP
* @param array $col_parametros Columnas que espera recibir el DAO.
* @param array $col_resultado Columnas del registro resultante que se tomarán para rellenar la tabla
* @param boolean $sincro_continua En cada pedido de página ejecuta el DAO para actualizar los valores de las columnas
* @param boolean $estricto Indica si es imperioso que la columna externa posea un estado o se
* permite que no posea valor.
*/
function activar_proceso_carga_externa_dao($metodo, $clase=null, $include=null, $col_parametros=[], $col_resultado=[], $sincro_continua=true, $estricto=true, $carga_masiva = 0, $metodo_masivo = '')
{
$proximo = count($this->_proceso_carga_externa);
$this->_proceso_carga_externa[$proximo]["tipo"] = "dao";
$this->_proceso_carga_externa[$proximo]["metodo"] = $metodo;
$this->_proceso_carga_externa[$proximo]["clase"] = $clase;
$this->_proceso_carga_externa[$proximo]["include"] = $include;
$this->_proceso_carga_externa[$proximo]["col_parametro"] = $col_parametros;
$this->_proceso_carga_externa[$proximo]["col_resultado"] = $col_resultado;
$this->_proceso_carga_externa[$proximo]["sincro_continua"] = $sincro_continua;
$this->_proceso_carga_externa[$proximo]["dato_estricto"] = $estricto;
$this->_proceso_carga_externa[$proximo]["permite_carga_masiva"] = $carga_masiva;
$this->_proceso_carga_externa[$proximo]["metodo_masivo"] = $metodo_masivo;
}
/**
* Carga una columna separada del proceso común de carga
* Se brinda un Datos Tabla que carga una o más columnas denominadas como 'externas'
* Una columna externa no participa en la sincronización posterior, pero por necesidades casi siempre estéticas
* necesitan mantenerse junto al conjunto de datos.
*
* @param string $tabla Identificador del objeto_datos_tabla a utilizar.
* @param string $metodo Método que obtiene los datos.
* @param array $col_parametros Columnas que espera recibir el DAO.
* @param array $col_resultado Columnas del registro resultante que se tomarán para rellenar la tabla
* @param boolean $sincro_continua En cada pedido de página ejecuta el DAO para actualizar los valores de las columnas
* @param boolean $estricto Indica si es imperioso que la columna externa posea un estado o se
* permite que no posea valor.
*/
function activar_proceso_carga_externa_datos_tabla($tabla, $metodo, $col_parametros, $col_resultado, $sincro_continua=true, $estricto=true, $carga_masiva = 0, $metodo_masivo = '')
{
$proximo = count($this->_proceso_carga_externa);
$this->_proceso_carga_externa[$proximo]["tipo"] = "d_t";
$this->_proceso_carga_externa[$proximo]["tabla"] = $tabla;
$this->_proceso_carga_externa[$proximo]["metodo"] = $metodo;
$this->_proceso_carga_externa[$proximo]["col_parametro"] = $col_parametros;
$this->_proceso_carga_externa[$proximo]["col_resultado"] = $col_resultado;
$this->_proceso_carga_externa[$proximo]["sincro_continua"] = $sincro_continua;
$this->_proceso_carga_externa[$proximo]["dato_estricto"] = $estricto;
$this->_proceso_carga_externa[$proximo]["permite_carga_masiva"] = $carga_masiva;
$this->_proceso_carga_externa[$proximo]["metodo_masivo"] = $metodo_masivo;
}
function activar_proceso_carga_externa_consulta_php($metodo, $id_consulta_php, $col_parametros, $col_resultado, $sincro_continua=true, $estricto=true, $carga_masiva=0, $metodo_masivo = '')
{
$proximo = count($this->_proceso_carga_externa);
$this->_proceso_carga_externa[$proximo]["tipo"] = "ccp";
$this->_proceso_carga_externa[$proximo]["metodo"] = $metodo;
$this->_proceso_carga_externa[$proximo]["clase"] = $id_consulta_php;
$this->_proceso_carga_externa[$proximo]["col_parametro"] = $col_parametros;
$this->_proceso_carga_externa[$proximo]["col_resultado"] = $col_resultado;
$this->_proceso_carga_externa[$proximo]["sincro_continua"] = $sincro_continua;
$this->_proceso_carga_externa[$proximo]["dato_estricto"] = $estricto;
$this->_proceso_carga_externa[$proximo]["permite_carga_masiva"] = $carga_masiva;
$this->_proceso_carga_externa[$proximo]["metodo_masivo"] = $metodo_masivo;
}
/**
* Activa el mecanismo de baja lógica
* En este mecanismo en lugar de hacer DELETES actualiza una columna
*
* @param string $columna Columna que determina la baja lógica
* @param mixed $valor Valor que toma la columna al dar de baja un registro
*/
function activar_baja_logica($columna, $valor)
{
$this->_baja_logica = true;
$this->_baja_logica_columna = $columna;
$this->_baja_logica_valor = $valor;
}
/**
* Permite que las modificaciones puedan cambiar las claves del registro
*/
function activar_modificacion_clave()
{
$this->_flag_modificacion_clave = true;
}
function set_schema($schema)
{
$this->_schema = $schema;
}
/**
* Activa/Desactiva el uso automático del trim sobre datos en el insert o update
* @param boolean $usar
*/
function set_usar_trim($usar)
{
$this->_hacer_trim_datos = $usar;
}
function get_usar_trim()
{
return $this->_hacer_trim_datos;
}
/**
* Activa/Desactiva un mecanismo de chequeo de concurrencia en la edición
*/
function set_lock_optimista($usar=true)
{
$this->_lock_optimista = $usar;
}
function get_lock_optimista()
{
return $this->_lock_optimista;
}
/**
* recibe una columna y una tabla y devuelve verdadero si la columna
* pertenece a la tabla y falso en caso contrario
*/
function pertenece_a_tabla(&$col, $tabla)
{
// si no está seteado $col['tabla'] entonces no puede ser un ap
// multitabla, por tanto todas las columnas perteneces a la tabla $this->_tabla
// porque no hay otra ;)
return !isset($col['tabla']) || $tabla == $col['tabla'];
}
protected function agregar_schema($elemento, $es_externa = false)
{
$resultado = (is_null($this->_schema)) ? $elemento : $this->_schema . '.' . $elemento;
return $resultado;
}
//-------------------------------------------------------------------------------
//------ CARGA ----------------------------------------------------------------
//-------------------------------------------------------------------------------
/**
* Carga el datos_tabla asociado restringiendo POR valores especificos de campos de la tabla
*
* @param array $clave Arreglo asociativo campo-valor
* @param boolean $anexar_datos Si es false borra todos los datos actuales de la tabla, sino los mantiene y adjunto los nuevos
* @param boolean $usar_cursores En caso de anexar datos, fuerza a que los padres de la fila sean los cursores actuales de las tablas padre
* @return boolean Falso si no se encontro ningun registro
*/
function cargar_por_clave($clave, $anexar_datos=false, $usar_cursores=false)
{
toba_asercion::es_array($clave, "Error cargando la tabla <b>$this->_tabla</b>, se esperaba un arreglo asociativo por ejemplo ".
"<pre>\$tabla->cargar(array('campo'=> 'valor'))</pre>", true);
$where = $this->generar_clausula_where($clave);
return $this->cargar_con_where_from_especifico($where, null, $anexar_datos, $usar_cursores);
}
/**
* Carga el datos_tabla asociaciado a partir de una clausula where personalizada
* @param string $clausula Cláusula where que será anexada con un AND a las cláusulas básicas de la tabla
* @param boolean $anexar_datos Si es false borra todos los datos actuales de la tabla, sino los mantiene y adjunto los nuevos
* @param boolean $usar_cursores En caso de anexar datos, fuerza a que los padres de la fila sean los cursores actuales de las tablas padre
* @return boolean Falso si no se encontro ningun registro
*/
function cargar_con_where($clausula, $anexar_datos=false, $usar_cursores=false)
{
$where_basico = $this->generar_clausula_where();
if (trim($clausula) != '') {
$where_basico[] = $clausula;
}
return $this->cargar_con_where_from_especifico($where_basico, null, $anexar_datos, $usar_cursores);
}
/**
* Carga el datos_tabla asociado CON clausulas WHERE y FROM especificas, el entorno no incide en ellas
* @param array $where Clasulas que seran concatenadas con un AND
* @param array $from Tablas extra que participan (la actual se incluye automaticamente)
* @param boolean $anexar_datos Si es false borra todos los datos actuales de la tabla, sino los mantiene y adjunto los nuevos
* @param boolean $usar_cursores En caso de anexar datos, fuerza a que los padres de la fila sean los cursores actuales de las tablas padre
* @return boolean Falso si no se encontro ningún registro
*/
function cargar_con_where_from_especifico($where=null, $from=null, $anexar_datos=false, $usar_cursores=false)
{
toba_asercion::es_array_o_null($where,"AP [{$this->_tabla}] El WHERE debe ser un array");
toba_asercion::es_array_o_null($from,"AP [{$this->_tabla}] El FROM debe ser un array");
$sql = $this->generar_sql_select($where, $from);
return $this->cargar_con_sql($sql, $anexar_datos, $usar_cursores);
}
/**
* Carga el datos_tabla asociado CON una query SQL directa
* @param boolean $anexar_datos Si es false borra todos los datos actuales de la tabla, sino los mantiene y adjunto los nuevos
* @param boolean $usar_cursores En caso de anexar datos, fuerza a que los padres de la fila sean los cursores actuales de las tablas padre
* @return boolean Falso si no se encontro ningún registro
*/
function cargar_con_sql($sql, $anexar_datos=false, $usar_cursores=false)
{
$this->log("SQL de carga: \n" . $sql."\n");
try{
$db = toba::db($this->_fuente);
$datos = $db->consultar($sql);
}catch(toba_error_db $e){
$mensaje = $e->get_mensaje_motor();
$mensaje = "Error cargando la tabla <b>$this->_tabla</b>, a continuación el mensaje de la base:<br>".$mensaje;
$e->set_mensaje_motor($mensaje);
toba::logger()->error( get_class($this). ' - '.
'Error cargando datos. ' .$e->getMessage() );
throw $e;
}
return $this->cargar_con_datos($datos, $anexar_datos, $usar_cursores);
}
/**
* Carga el datos_tabla asociado CON un conjunto de datos especifico
* @param array $datos Datos a cargar en formato RecordSet. No incluye las columnas externas.
* @param boolean $anexar_datos Si es false borra todos los datos actuales de la tabla, sino los mantiene y adjunto los nuevos
* @param boolean $usar_cursores En caso de anexar datos, fuerza a que los padres de la fila sean los cursores actuales de las tablas padre
* @return boolean Falso si no se encontro ningún registro
*/
function cargar_con_datos($datos, $anexar_datos=false, $usar_cursores=false)
{
if(count($datos)>0){
//Si existen campos externos, los recupero.
if ($this->objeto_tabla->posee_columnas_externas()) {
//Aca hay que decidir que hacer si la tabla ya tiene datos o si es la carga inicial
if (! $this->objeto_tabla->esta_cargada()) { //Seria la carga inicial?
$datos = $this->carga_inicial_campos_externos($datos);
} else {
for ($a=0;$a<count($datos);$a++) {
$campos_externos = $this->completar_campos_externos_fila($datos[$a]);
foreach ($campos_externos as $id => $valor) {
$datos[$a][$id] = $valor;
}
}
}
}
// Lleno la TABLA
if ( $anexar_datos ) {
$this->objeto_tabla->anexar_datos($datos, $usar_cursores);
} else {
$this->objeto_tabla->cargar_con_datos($datos);
}
//ei_arbol($datos);
return true;
}else{
//No se carga nada!
$this->log(" FILAS: 0" );
return false;
}
}
//-------------------------------------------------------------------------------
//------ SINCRONIZACION -------------------------------------------------------
//-------------------------------------------------------------------------------
/**
* Sincroniza los cambios en los registros de esta tabla con la base de datos
* Sólo se utiliza cuando la tabla no está involucrada en algun datos_relacion, sino
* la sincronización es guiada por ese objeto
* @return integer Cantidad de registros modificados
* @throws toba_error En case de error en la sincronizacion, se aborta la transaccion (si se esta utilizando)
*/
function sincronizar($filas=array())
{
$this->log("Inicio SINCRONIZAR");
try{
if($this->_utilizar_transaccion) abrir_transaccion($this->_fuente);
$this->evt__pre_sincronizacion();
$modificaciones = 0;
$modificaciones += $this->sincronizar_eliminados($filas);
$modificaciones += $this->sincronizar_insertados($filas);
$modificaciones += $this->sincronizar_actualizados($filas);
$this->evt__post_sincronizacion();
if($this->_utilizar_transaccion) cerrar_transaccion($this->_fuente);
$this->log("Fin SINCRONIZAR: $modificaciones.");
return $modificaciones;
} catch(toba_error $e) {
if($this->_utilizar_transaccion) {
toba::logger()->info("Abortando transacción en {$this->_fuente}", 'toba');
abortar_transaccion($this->_fuente);
}
toba::logger()->debug("Relanzando excepción. ".$e, 'toba');
throw $e;
}
}
/**
* Sincroniza con la BD los registros borrados en esta tabla
* @return integer Cantidad de modificaciones a la base
*/
function sincronizar_eliminados($filas=array())
{
$this->get_estado_datos_tabla();
$modificaciones = 0;
if($filas) {
$registros = $filas;
}else{
$registros = array_keys($this->_cambios);
}
foreach($registros as $registro){
if ($this->_cambios[$registro]['estado'] == 'd') {
$this->evt__pre_delete($registro);
$this->eliminar_registro_db($registro);
$this->evt__post_delete($registro);
$modificaciones ++;
}
}
return $modificaciones;
}
/**
* Sincroniza con la BD aquellos registros que suponen altas
* @return integer Cantidad de modificaciones a la base
*/
function sincronizar_insertados($filas=array())
{
$this->get_estado_datos_tabla();
$modificaciones = 0;
if($filas) {
$registros = $filas;
}else{
$registros = array_keys($this->_cambios);
}
foreach($registros as $registro){
if ($this->_cambios[$registro]['estado'] == "i") {
$this->evt__pre_insert($registro);
$this->insertar_registro_db($registro);
$this->evt__post_insert($registro);
$modificaciones ++;
}
}
//Seteo en la TABLA los datos generados durante la sincronizacion
$this->actualizar_columnas_predeterminadas_db($filas);
return $modificaciones;
}
/**
* Sincroniza con la BD aquellos registros que suponen actualizaciones
* @return integer Cantidad de modificaciones a la base
*/
function sincronizar_actualizados($filas=array())
{
$this->get_estado_datos_tabla();
$modificaciones = 0;
if($filas) {
$registros = $filas;
}else{
$registros = array_keys($this->_cambios);
}
foreach($registros as $registro) {
if ($this->_cambios[$registro]['estado'] == 'u') {
$this->evt__pre_update($registro);
$this->modificar_registro_db($registro);
$this->evt__post_update($registro);
$modificaciones ++;
}
}
return $modificaciones;
}
//-------------------------------------------------------------------------------
//------ COMANDOS DE SINCRO------------------------------------------------------
//-------------------------------------------------------------------------------
/**
* Inserta un registro en la base y recupera su secuencia si la tiene
* @param mixed $id_registro Clave interna del registro
* @ignore
*/
protected function insertar_registro_db($id_registro)
{
$this->_insert_campos_default = array();
$this->ejecutar_sql_insert($id_registro);
//Actualizo las secuencias
if(count($this->_secuencias)>0) {
foreach($this->_secuencias as $columna => $secuencia) {
if ($this->es_seq_tabla_ext($columna)) {
continue;
}
$secuencia = $this->agregar_schema($secuencia);
$valor = recuperar_secuencia($secuencia, $this->_fuente);
//El valor es necesario en el evt__post_insert!!
$this->datos[$id_registro][$columna] = $valor;
$this->registrar_recuperacion_valor_db( $id_registro, $columna, $valor );
}
}
//Actualizo los valores que tomaron los DEFAULT enviados
if (! empty($this->_insert_campos_default)) {
$id = array();
foreach ($this->_clave as $campo_clave) {
$id[$campo_clave] = $this->datos[$id_registro][$campo_clave];
}
$where = $this->generar_clausula_where_lineal($id, false);
$sql = $this->get_sql_campos_default($where);
$fila_base = toba::db($this->_fuente)->consultar_fila($sql);
if ($fila_base === false) {
throw new toba_error('Se esperaba encontrar un registro', $sql);
}
foreach ($this->_insert_campos_default as $campo) {
$this->registrar_recuperacion_valor_db($id_registro, $campo, $fila_base[$campo]);
}
}
}
abstract protected function es_seq_tabla_ext($col);
abstract protected function get_sql_campos_default($where);
/**
* Ejecuta un update de un registro en la base
* @param mixed $id_registro Clave interna del registro
* @ignore
*/
protected function modificar_registro_db($id_registro)
{
$this->ejecutar_sql_update($id_registro);
}
/**
* Ejecuta un delete de un registro en la base
* @param mixed $id_registro Clave interna del registro
* @ignore
*/
protected function eliminar_registro_db($id_registro)
{
$sql = $this->generar_sql_delete($id_registro);
$this->log("registro: $id_registro - " . $sql);
$this->ejecutar_sql($sql, $id_registro);
return $sql;
}
/**
* Registra el valor generado por el motor de un columna
* @param string $id_registro Id. interno del registro
* @ignore
*/
protected function registrar_recuperacion_valor_db($id_registro, $columna, $valor)
{
$this->_columnas_predeterminadas_db[$id_registro][$columna] = $valor;
}
/**
* Actualiza en los registros los valores generados por el motor durante la transacción
* @ignore
*/
protected function actualizar_columnas_predeterminadas_db($filas=array())
{
if(isset($this->_columnas_predeterminadas_db)){
foreach( $this->_columnas_predeterminadas_db as $id_registro => $columnas ){
if($filas && !in_array($id_registro,$filas)) {
continue;
}
foreach( $columnas as $columna => $valor ){
$this->objeto_tabla->set_fila_columna_valor($id_registro, $columna, $valor);
}
}
unset($this->_columnas_predeterminadas_db);
}
}
/**
* Ventana para incluír validaciones (disparar una excepcion) o disparar procesos previo a sincronizar con la base de datos
* La transacción con la bd ya fue iniciada (si es que esta definida)
* @ventana
*/
function evt__pre_sincronizacion(){}
/**
* Ventana para incluír validaciones (disparar una excepcion) o disparar procesos antes de terminar de sincronizar con la base de datos
* La transacción con la bd aún no se terminó (si es que esta definida)
* @ventana
*/
function evt__post_sincronizacion(){}
/**
* Ventana para manejar la pérdida de sincronización con la tabla en la base de datos
* El escenario es que ejecuto un update/delete usando los valores de las columnas originales y no arrojo resultados, con lo que se asume que alguien más modifico el registro en el medio
* La transacción con la bd aún no se terminó (si es que esta definida)
*
* @param integer $id_fila Id. de fila de la tabla en la cual se encontró el problema
* @param string $sql_origen Sentencia que se intento ejecutar
* @ventana
*/
function evt__perdida_sincronizacion($id_fila, $sql_origen)
{
$mensaje_usuario = "Error de concurrencia en la edición de los datos.<br><br>".
"Mientras Ud. editaba esta información, la misma fue modificada por alguien más. ".
"Para garantizar consistencia sólo podrá guardar cambios luego de reiniciar la edición.<br>";
//--Hace una consulta SQL contra la tabla para averiguar puntualmente cuál fue el cambio que llevo a esta situación
$columnas = array();
foreach ($this->_columnas as $col) {
if(!$col['externa'] && $col['tipo'] != 'B') {
$columnas[] = $col['columna'];
}
}
$id = array();
foreach($this->_clave as $clave){
$id[$clave] = $this->_cambios[$id_fila]['clave'][$clave];
}
$where = $this->generar_clausula_where_lineal($id, false);
$sql = "SELECT\n\t" . implode(", \n\t", $columnas);
$sql .= "\nFROM\n\t " . $this->agregar_schema($this->_tabla);
$sql .= "\nWHERE ".implode(' AND ', $where);
$fila_base = toba::db($this->_fuente)->consultar_fila($sql);
//-- Averigua que cambio
if ($fila_base === false) {
$diff = "La fila '$id_fila' no existe en la base, fue borrada";
} else {
$fila_original = $this->_cambios[$id_fila]['original'];
$diff = "<ul>";
foreach ($columnas as $col) {
if (! isset($fila_base[$col])) {
$fila_base[$col] = null;
}
if (! isset($fila_original[$col])) {
$fila_original[$col] = null;
}
$modificado = (string) $fila_base[$col] !== (string) $fila_original[$col];
if ($modificado) {
$anterior = isset($fila_original[$col]) ? "'".$fila_original[$col]."'" : 'NULL';
$actual = isset($fila_base[$col]) ? "'".$fila_base[$col]."'" : 'NULL';
$diff .= "<li>$col: tenía el valor $anterior y ahora tiene $actual </li>";
}
}
$diff .= '</ul>';
}
$mensaje_debug = '';
$mensaje_debug .= "<p><b>Tabla:</b> {$this->_tabla}</p>";
$mensaje_debug .= "<p><b>Diff de datos:</b> Cambios en fila $id_fila ".$diff."</p>";
$mensaje_debug .= "<p><b>SQL:</b> $sql_origen</p>";
throw new toba_error_usuario($mensaje_usuario, $mensaje_debug);
}
/**
* Ventana de extensión previo a la inserción de un registro durante una sincronización con la base
* @param mixed $id_registro Clave interna del registro
* @ventana
*/
protected function evt__pre_insert($id_registro){}
/**
* Ventana de extensión posterior a la inserción de un registro durante una sincronización con la base
* @param mixed $id_registro Clave interna del registro
* @ventana
*/
protected function evt__post_insert($id_registro){}
/**
* Ventana de extensión previo a la actualización de un registro durante una sincronización con la base
* @param mixed $id_registro Clave interna del registro
* @ventana
*/
protected function evt__pre_update($id_registro){}
/**
* Ventana de extensión posterior a la actualización de un registro durante una sincronización con la base
* @param mixed $id_registro Clave interna del registro
* @ventana
*/
protected function evt__post_update($id_registro){}
/**
* Ventana de extensión previa al borrado de un registro durante una sincronización con la base
* @param mixed $id_registro Clave interna del registro
* @ventana
*/
protected function evt__pre_delete($id_registro){}
/**
* Ventana de extensión posterior al borrado de un registro durante una sincronización con la base
* @param mixed $id_registro Clave interna del registro
* @ventana
*/
protected function evt__post_delete($id_registro){}
//-------------------------------------------------------------------------------
//------ Servicios SQL --------------------------------------------------------
//-------------------------------------------------------------------------------
/**
* Shortcut de @see toba_db::ejecutar()
*/
protected function ejecutar_sql($sql, $id_fila=null)
{
$sen = toba::db($this->_fuente)->sentencia_preparar($sql);
$reg = toba::db($this->_fuente)->sentencia_ejecutar($sen);
if ($this->_lock_optimista && isset($id_fila) && $reg == 0) {
$this->evt__perdida_sincronizacion($id_fila, $sql);
}
}
protected function ejecutar_con_binarios($sql, $binarios, $id_fila = null)
{
$sen = toba::db($this->_fuente)->sentencia_preparar($sql);
toba::db($this->_fuente)->sentencia_agregar_binarios($sen, $binarios);
$reg = toba::db($this->_fuente)->sentencia_ejecutar($sen);
toba::db($this->_fuente)->sentencia_eliminar($sen);
if ($this->_lock_optimista && isset($id_fila) && $reg == 0) {
$this->evt__perdida_sincronizacion($id_fila, $sql);
}
}
/**
* Genera la sentencia WHERE del estilo ( nombre_columna = valor ) respetando el tipo de datos
* @param array $clave Arreglo asociativo clave - valor de la clave a filtrar
* @param boolean $alias Útil para cuando se generan SELECTs complejos
* @return array Clausulas where
*
* @ignore
*/
protected function generar_clausula_where_lineal($clave,$alias=true)
{
if ($alias) {
$tabla_alias = isset($this->_alias) ? $this->_alias . "." : "";
} else {
$tabla_alias = "";
}
$clausula = array();
foreach($clave as $columna => $valor) {
if (isset($valor)) {
if (is_bool($valor)) {
$valor = ($valor) ? 'true' : 'false';
}
$valor = toba::db($this->_fuente)->quote($valor);
$clausula[] = "$tabla_alias" . "$columna = $valor";
} else {
$clausula[] = "$tabla_alias" . "$columna IS NULL";
}
}
return $clausula;
}
/**
* Genera la sentencia WHERE del estilo ( nombre_columna = valor ) respetando el tipo de datos
* y las asociaciones con los padres
* @param array $clave Arreglo asociativo clave - valor de la clave a filtrar
* @return array Clausulas where
*
* @ignore
*/
protected function generar_clausula_where($clave=array())
{
$clausula = $this->generar_clausula_where_lineal($clave, true);
//Si la tabla tiene relaciones con padres
//Se hace un subselect con los campos relacionados
foreach ( $this->objeto_tabla->get_relaciones_con_padres() as $rel_padre) {
$nuevo = $rel_padre->generar_clausula_subselect($this->_alias);
if (isset($nuevo)) {
$clausula[] = $nuevo;
}
}
return $clausula;
}
/**
* @param array $where Clasulas que seran concatenadas con un AND
* @param array $from Tablas extra que participan (la actual se incluye automaticamente)
* @return string Consulta armada
* @ignore
*/
protected function generar_sql_select($where=array(), $from=null, $columnas=null)
{
//Si no se explicitan las columnas, se asume que son todas
if (!isset($columnas)) {
$columnas = array();
foreach ($this->_columnas as $col) {
if(!$col['externa'] && $col['tipo'] != 'B') {
$columnas[] = $this->get_select_col($col['columna']);
}
}
}
//Si no se explicitan los from se asume que es la tabla local
if (!isset($from)) {
$from = array($this->get_from_default());
}
$sql = "SELECT\n\t" . implode(", \n\t", $columnas);
$sql .= "\nFROM\n\t" . implode(", ", $from);
if(! empty($where)) {
$sql .= "\nWHERE";
foreach ($where as $clausula) {
$sql .= "\n\t$clausula AND";
}
$sql = substr($sql, 0, -4); //Se saca el ultimo AND
}
if ($this->_usar_perfil_de_datos) { //Si el datos_tabla maneja perfil de datos
$sql = toba::perfil_de_datos()->filtrar($sql);
}
//Se guardan los datos de la carga
$this->_sql_carga = array('from' => $from, 'where' => $where);
return $sql;
}
abstract protected function get_select_col($col);
abstract protected function get_from_default();
/**
* Retorna la sentencia sql utilizada previamente para la carga de esta tabla, pero seleccionando solo algunos campos
* @param array $campos Columnas que se traen de la carga
*
* @ignore
*/
function get_sql_de_carga($campos)
{
if (isset($this->_sql_carga)) {
return $this->generar_sql_select($this->_sql_carga['where'], $this->_sql_carga['from'], $campos);
} else {
throw new toba_error_def("AP-TABLA Db: La tabla no ha sido cargada en este pedido de página");
}
}
/**
* @param mixed $id_registro Clave interna del registro
* @ignore
*/
protected function ejecutar_sql_insert($id_registro, $solo_retornar=false, $tabla = null, $cols_tabla = array(), $tabla_ext = false)
{
$a=0;
$registro = $this->datos[$id_registro];
$db = toba::db($this->_fuente);
//Arreglos donde se guardara la informacion
$binarios = array();
$valores_sql = array();
$columnas_sql = array();
$valores_sql_binarios = array();
$columnas_sql_binarios = array();
//Determinacion para el DT multitabla
$tabla = (is_null($tabla)) ? $this->_tabla : $tabla;
$columnas = (empty($cols_tabla)) ? $this->_columnas : $cols_tabla;
foreach($columnas as $columna) {
$col = $columna['columna'];
$es_insertable = (trim($columna['secuencia']=="")) && ($columna['externa'] != 1);
$es_binario = ($columna['tipo'] == 'B');
if( $es_insertable) {
if ($es_binario) {
$blob = $this->objeto_tabla->_get_blob_transaccion($id_registro, $col);
//-- Si no esta seteado es un blob nulo
if ($blob === false) {
$valores_sql[$a] = "NULL";
$columnas_sql[$a] = $col;
} elseif (is_resource($blob)) {
$binarios[] = $blob;
$valores_sql_binarios[$a] = '?';
$columnas_sql_binarios[$a] = $col;
} else {
//No tocar nada
}
} elseif ( !isset($registro[$col]) || $registro[$col] === NULL ) {
//-- Es un campo NULO
$valores_sql[$a] = $db->get_semantica_valor_defecto();
$columnas_sql[$a] = $col;
$this->_insert_campos_default[] = $col;
} else {
if (is_bool($registro[$col])) { //Si es un valor booleano lo transformo a entero
if ($registro[$col] === true) {
$registro[$col] = 1;
} elseif ($registro[$col] === false) {
$registro[$col] = 0;
}
}
if ($this->_hacer_trim_datos) {
$valores_sql[$a] = $db->quote(trim($registro[$col]));
} else {
$valores_sql[$a] = $db->quote($registro[$col]);
}
$columnas_sql[$a] = $col;
}
$a++;
}
}
// Para evitar un "bug" de PDO se colocan los campos de tipo binario al inicio de la sentencia INSERT.
$valores_sql = array_merge($valores_sql_binarios, $valores_sql);
$columnas_sql = array_merge($columnas_sql_binarios, $columnas_sql);
$sql = "INSERT INTO " . $this->agregar_schema($tabla, $tabla_ext) .
" ( " . implode(", ", $columnas_sql) . " ) ".
"\n VALUES (" . implode(", ", $valores_sql) . ");";
if ($solo_retornar) {
return $sql;
}
$this->log("registro: $id_registro - " . $sql);
if (empty($binarios)) {
$this->ejecutar_sql($sql);
} else {
$this->ejecutar_con_binarios($sql, $binarios);
}
}
abstract protected function get_flag_mod_clave();
/**
* @param mixed $id_registro Clave interna del registro
* @ignore
*/
protected function ejecutar_sql_update($id_registro, $tabla = null, $where = null, $cols_tabla = array(), $tabla_ext = false)
{
$binarios = array();
$registro = $this->datos[$id_registro];
$cambios_reales = $this->objeto_tabla->get_cambios_fila($id_registro, $registro);
$tabla = (is_null($tabla)) ? $this->_tabla : $tabla;
$columnas = (empty($cols_tabla)) ? $this->_columnas : $cols_tabla;
//Genero las sentencias de la clausula SET para cada columna
$set = array();
$db = toba::db($this->_fuente);
foreach($columnas as $columna) {