Revision 44403
trunk/org.gvsig.desktop/org.gvsig.desktop.compat.cdc/org.gvsig.fmap.dal/org.gvsig.fmap.dal.db/org.gvsig.fmap.dal.db.jdbc/src/main/java/org/gvsig/fmap/dal/store/jdbc2/spi/JDBCHelperBase.java | ||
---|---|---|
83 | 83 |
|
84 | 84 |
private JDBCConnectionParameters connectionParameters; |
85 | 85 |
|
86 |
private JDBCStoreProvider store;
|
|
87 |
|
|
86 |
private JDBCStoreProvider store; |
|
87 |
|
|
88 | 88 |
protected OperationsFactory operationsFactory = null; |
89 |
|
|
89 |
|
|
90 | 90 |
protected SRSSolver srssolver; |
91 |
|
|
91 |
|
|
92 | 92 |
public JDBCHelperBase(JDBCConnectionParameters connectionParameters) { |
93 | 93 |
this.connectionParameters = connectionParameters; |
94 |
|
|
94 |
|
|
95 | 95 |
// If a particular treatment is required to convert SRS to the |
96 | 96 |
// BBDD format, overwrite JDBCSRSsBase and use it instead of JDBCSRSsDumb. |
97 | 97 |
this.srssolver = new SRSSolverDumb(this); |
... | ... | |
101 | 101 |
ResourceConsumer helperClient, |
102 | 102 |
JDBCConnectionParameters connectionParameters, |
103 | 103 |
JDBCStoreProvider store |
104 |
) {
|
|
104 |
) { |
|
105 | 105 |
this.store = store; |
106 | 106 |
this.helperClient = helperClient; |
107 | 107 |
this.connectionParameters = connectionParameters; |
108 |
initializeResource(connectionParameters);
|
|
108 |
initializeResource(connectionParameters); |
|
109 | 109 |
} |
110 |
|
|
110 |
|
|
111 | 111 |
protected String getResourceType() { |
112 | 112 |
return JDBCResource.NAME; |
113 | 113 |
} |
... | ... | |
116 | 116 |
public String getProviderName() { |
117 | 117 |
return JDBCLibrary.NAME; |
118 | 118 |
} |
119 |
|
|
119 |
|
|
120 | 120 |
@Override |
121 | 121 |
public GeometrySupportType getGeometrySupportType() { |
122 | 122 |
// El proveedor generico de JDBC guadara las geoemtrias como WKT |
... | ... | |
134 | 134 |
public boolean allowNestedOperations() { |
135 | 135 |
return false; |
136 | 136 |
} |
137 |
|
|
137 |
|
|
138 | 138 |
@Override |
139 | 139 |
public boolean canWriteGeometry(int geometryType, int geometrySubtype) { |
140 | 140 |
// Como va a guardar las geometrias en WKT, puede escribirlas todas. |
... | ... | |
161 | 161 |
// Asumimos que la BBDD soporta OFFSET |
162 | 162 |
return true; |
163 | 163 |
} |
164 |
|
|
164 |
|
|
165 | 165 |
@Override |
166 | 166 |
public String getQuoteForStrings() { |
167 | 167 |
return QUOTE_FOR_USE_IN_STRINGS; |
168 | 168 |
} |
169 | 169 |
|
170 | 170 |
protected boolean supportCaller(Code.Caller caller) { |
171 |
if( StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE") ) {
|
|
171 |
if (StringUtils.equalsIgnoreCase(caller.name(), "FOREING_VALUE")) {
|
|
172 | 172 |
return true; |
173 | 173 |
} |
174 | 174 |
Function function = caller.function(); |
175 |
if( function==null ) {
|
|
175 |
if (function == null) {
|
|
176 | 176 |
return false; |
177 | 177 |
} |
178 |
if( !function.isSQLCompatible() ) {
|
|
178 |
if (!function.isSQLCompatible()) {
|
|
179 | 179 |
return false; |
180 | 180 |
} |
181 |
if( !this.hasSpatialFunctions() ) {
|
|
182 |
if( StringUtils.equalsIgnoreCase(function.group(),Function.GROUP_OGC) ) {
|
|
181 |
if (!this.hasSpatialFunctions()) {
|
|
182 |
if (StringUtils.equalsIgnoreCase(function.group(), Function.GROUP_OGC)) {
|
|
183 | 183 |
return false; |
184 | 184 |
} |
185 | 185 |
} |
186 | 186 |
return true; |
187 | 187 |
} |
188 |
|
|
188 |
|
|
189 | 189 |
@Override |
190 | 190 |
public boolean supportFilter(final FeatureType type, Evaluator filter) { |
191 | 191 |
// No podemos filtrar cuando: |
... | ... | |
198 | 198 |
// Un proveedor especifico podria sobreescribir el metodo, |
199 | 199 |
// para hilar mas fino al comprobar si soporta el filtro o no. |
200 | 200 |
// |
201 |
|
|
201 |
|
|
202 | 202 |
if (this.useSubquery()) { |
203 | 203 |
// Se esta usando una subquery en los parametros de acceso a la |
204 | 204 |
// BBDD, asi que no podemos filtrar. |
... | ... | |
229 | 229 |
@Override |
230 | 230 |
public void visit(Object code_obj) throws VisitCanceledException, BaseException { |
231 | 231 |
Code code = (Code) code_obj; |
232 |
switch(code.code()) { |
|
232 |
switch (code.code()) {
|
|
233 | 233 |
case Code.CALLER: |
234 | 234 |
Code.Caller caller = (Code.Caller) code; |
235 |
if( !supportCaller(caller) ) {
|
|
235 |
if (!supportCaller(caller)) {
|
|
236 | 236 |
isCompatible.setValue(false); |
237 | 237 |
throw new VisitCanceledException(); |
238 | 238 |
} |
239 | 239 |
break; |
240 |
|
|
240 |
|
|
241 | 241 |
case Code.IDENTIFIER: |
242 | 242 |
Code.Identifier identifier = (Code.Identifier) code; |
243 |
if( type!=null ) {
|
|
243 |
if (type != null) {
|
|
244 | 244 |
FeatureAttributeDescriptor attrdesc = type.getAttributeDescriptor(identifier.name()); |
245 |
if( attrdesc!=null ) {
|
|
246 |
if( attrdesc.isComputed() ) {
|
|
245 |
if (attrdesc != null) {
|
|
246 |
if (attrdesc.isComputed()) {
|
|
247 | 247 |
isCompatible.setValue(false); |
248 | 248 |
throw new VisitCanceledException(); |
249 | 249 |
} |
... | ... | |
259 | 259 |
} catch (Exception ex) { |
260 | 260 |
LOGGER.warn("Can't calculate if is SQL compatible.", ex); |
261 | 261 |
} |
262 |
|
|
262 |
|
|
263 | 263 |
return isCompatible.booleanValue(); |
264 | 264 |
} |
265 |
|
|
265 |
|
|
266 | 266 |
@Override |
267 | 267 |
public boolean supportOrder(FeatureType type, FeatureQueryOrder order) { |
268 | 268 |
if (this.useSubquery()) { |
... | ... | |
271 | 271 |
if (order == null) { |
272 | 272 |
return true; |
273 | 273 |
} |
274 |
for( FeatureQueryOrder.FeatureQueryOrderMember member : order.members() ) {
|
|
274 |
for (FeatureQueryOrder.FeatureQueryOrderMember member : order.members()) {
|
|
275 | 275 |
if (member.hasEvaluator()) { |
276 |
if( !this.supportFilter(type, member.getEvaluator()) ) {
|
|
276 |
if (!this.supportFilter(type, member.getEvaluator())) {
|
|
277 | 277 |
return false; |
278 | 278 |
} |
279 |
}
|
|
279 |
} |
|
280 | 280 |
} |
281 | 281 |
return true; |
282 | 282 |
} |
283 |
|
|
283 |
|
|
284 | 284 |
@Override |
285 | 285 |
public OperationsFactory getOperations() { |
286 |
if (this.operationsFactory== null) { |
|
286 |
if (this.operationsFactory == null) {
|
|
287 | 287 |
this.operationsFactory = new OperationsFactoryBase(this); |
288 | 288 |
} |
289 | 289 |
return operationsFactory; |
290 | 290 |
} |
291 |
|
|
291 |
|
|
292 | 292 |
protected void initializeResource(JDBCConnectionParameters params) { |
293 | 293 |
// Object[] resourceParams = new Object[]{ |
294 | 294 |
// params.getUrl(), |
... | ... | |
320 | 320 |
public String getSourceId() { |
321 | 321 |
return this.store.getSourceId(); |
322 | 322 |
} |
323 |
|
|
323 |
|
|
324 | 324 |
@Override |
325 | 325 |
public JDBCResource getResource() { |
326 | 326 |
return null; |
... | ... | |
349 | 349 |
|
350 | 350 |
@Override |
351 | 351 |
public void closeConnection(Connection connection) { |
352 |
if( connection != null ) {
|
|
353 |
LOGGER.debug("Clossing connection "+connection.hashCode());
|
|
352 |
if (connection != null) {
|
|
353 |
LOGGER.debug("Clossing connection " + connection.hashCode());
|
|
354 | 354 |
try { |
355 | 355 |
connection.close(); |
356 |
} catch(Exception ex) { |
|
356 |
} catch (Exception ex) {
|
|
357 | 357 |
LOGGER.warn("Can't close connection.", ex); |
358 | 358 |
} |
359 |
} |
|
359 |
}
|
|
360 | 360 |
} |
361 | 361 |
|
362 | 362 |
@Override |
363 | 363 |
public void closeConnectionQuietly(Connection connection) { |
364 |
if( connection != null ) {
|
|
365 |
LOGGER.debug("Clossing connection quietly "+connection.hashCode());
|
|
364 |
if (connection != null) {
|
|
365 |
LOGGER.debug("Clossing connection quietly " + connection.hashCode());
|
|
366 | 366 |
try { |
367 | 367 |
connection.close(); |
368 |
} catch(Exception ex) { |
|
368 |
} catch (Exception ex) {
|
|
369 | 369 |
LOGGER.warn("Can't close connection.", ex); |
370 | 370 |
} |
371 |
} |
|
371 |
}
|
|
372 | 372 |
} |
373 |
|
|
373 |
|
|
374 | 374 |
@Override |
375 | 375 |
protected void doDispose() throws BaseException { |
376 | 376 |
JDBCUtils.closeQuietly(this); |
... | ... | |
427 | 427 |
break; |
428 | 428 |
default: |
429 | 429 |
value = rs.getObject(rsIndex++); |
430 |
if( value instanceof Blob ) {
|
|
431 |
Blob blob = (Blob)value; |
|
430 |
if (value instanceof Blob) {
|
|
431 |
Blob blob = (Blob) value;
|
|
432 | 432 |
value = blob.getBytes(0, (int) blob.length()); |
433 | 433 |
blob.free(); |
434 | 434 |
} |
435 | 435 |
} |
436 | 436 |
feature.set(column.getIndex(), value); |
437 | 437 |
} |
438 |
if( ArrayUtils.isNotEmpty(extraValueNames) ) {
|
|
438 |
if (ArrayUtils.isNotEmpty(extraValueNames)) {
|
|
439 | 439 |
feature.setExtraValueNames(extraValueNames); |
440 | 440 |
for (int index = 0; index < extraValueNames.length; index++) { |
441 | 441 |
value = rs.getObject(rsIndex++); |
442 |
if( value instanceof Blob ) {
|
|
443 |
Blob blob = (Blob)value; |
|
442 |
if (value instanceof Blob) {
|
|
443 |
Blob blob = (Blob) value;
|
|
444 | 444 |
value = blob.getBytes(0, (int) blob.length()); |
445 | 445 |
blob.free(); |
446 | 446 |
} |
... | ... | |
494 | 494 |
public FeatureProvider createFeature(FeatureType featureType) throws DataException { |
495 | 495 |
return this.store.getStoreServices().createDefaultFeatureProvider(featureType); |
496 | 496 |
} |
497 |
|
|
497 |
|
|
498 | 498 |
@Override |
499 | 499 |
public boolean useSubquery() { |
500 |
if( this.store == null ) {
|
|
500 |
if (this.store == null) {
|
|
501 | 501 |
return false; |
502 | 502 |
} |
503 | 503 |
return !StringUtils.isEmpty(this.store.getParameters().getSQL()); |
504 |
}
|
|
505 |
|
|
504 |
} |
|
505 |
|
|
506 | 506 |
@Override |
507 | 507 |
public SRSSolver getSRSSolver() { |
508 | 508 |
return this.srssolver; |
509 | 509 |
} |
510 |
|
|
510 |
|
|
511 | 511 |
@Override |
512 | 512 |
public JDBCStoreProvider createProvider( |
513 |
JDBCStoreParameters parameters,
|
|
513 |
JDBCStoreParameters parameters, |
|
514 | 514 |
DataStoreProviderServices providerServices |
515 |
) throws InitializeException {
|
|
516 |
|
|
515 |
) throws InitializeException { |
|
516 |
|
|
517 | 517 |
JDBCStoreProviderBase theStore = new JDBCStoreProviderBase( |
518 |
parameters,
|
|
519 |
providerServices,
|
|
518 |
parameters, |
|
519 |
providerServices, |
|
520 | 520 |
DBHelper.newMetadataContainer(JDBCLibrary.NAME), |
521 | 521 |
this |
522 | 522 |
); |
... | ... | |
526 | 526 |
|
527 | 527 |
@Override |
528 | 528 |
public JDBCServerExplorer createServerExplorer( |
529 |
JDBCServerExplorerParameters parameters,
|
|
529 |
JDBCServerExplorerParameters parameters, |
|
530 | 530 |
DataServerExplorerProviderServices providerServices |
531 |
) throws InitializeException {
|
|
532 |
|
|
531 |
) throws InitializeException { |
|
532 |
|
|
533 | 533 |
JDBCServerExplorer explorer = new JDBCServerExplorerBase( |
534 |
parameters,
|
|
535 |
providerServices,
|
|
534 |
parameters, |
|
535 |
providerServices, |
|
536 | 536 |
this |
537 | 537 |
); |
538 | 538 |
this.initialize(explorer, parameters, null); |
... | ... | |
556 | 556 |
|
557 | 557 |
@Override |
558 | 558 |
public String getSourceId(JDBCStoreParameters parameters) { |
559 |
return parameters.getHost() + ":" +
|
|
560 |
parameters.getDBName() + ":" +
|
|
561 |
parameters.getSchema()+ ":" +
|
|
562 |
parameters.tableID(); |
|
559 |
return parameters.getHost() + ":" |
|
560 |
+ parameters.getDBName() + ":"
|
|
561 |
+ parameters.getSchema() + ":"
|
|
562 |
+ parameters.tableID();
|
|
563 | 563 |
} |
564 | 564 |
|
565 | 565 |
@Override |
566 | 566 |
public boolean isThreadSafe() { |
567 | 567 |
return true; |
568 | 568 |
} |
569 |
|
|
569 |
|
|
570 | 570 |
@Override |
571 | 571 |
public String[] replaceForeingValueFunction(SQLBuilder sqlbuilder, FeatureType type) { |
572 | 572 |
// See test SQLBuilderTest->testForeingValue() |
573 | 573 |
final ExpressionBuilder where = sqlbuilder.select().where(); |
574 |
if( where == null || where.isEmpty() ) {
|
|
574 |
if (where == null || where.isEmpty()) {
|
|
575 | 575 |
return null; |
576 | 576 |
} |
577 | 577 |
final SQLBuilder.TableNameBuilder table = sqlbuilder.select().from().table(); |
578 | 578 |
final ExpressionBuilder expbuilder = sqlbuilder.expression(); |
579 |
|
|
579 |
|
|
580 | 580 |
final List<String> foreing_value_args = new ArrayList<>(); |
581 | 581 |
final List<ExpressionBuilder.Value[]> value_replacements = new ArrayList<>(); |
582 | 582 |
|
... | ... | |
588 | 588 |
public void visit(ExpressionBuilder.Visitable value) { |
589 | 589 |
// Requiere que sea la funcion "FOREING_VALUE con un solo |
590 | 590 |
// argumento que sea una constante de tipo string. |
591 |
if( !(value instanceof ExpressionBuilder.Function) ) {
|
|
591 |
if (!(value instanceof ExpressionBuilder.Function)) {
|
|
592 | 592 |
return; |
593 | 593 |
} |
594 | 594 |
ExpressionBuilder.Function function = (ExpressionBuilder.Function) value; |
595 |
if( !StringUtils.equalsIgnoreCase(function.name(), "FOREING_VALUE") ) {
|
|
595 |
if (!StringUtils.equalsIgnoreCase(function.name(), "FOREING_VALUE")) {
|
|
596 | 596 |
return; |
597 | 597 |
} |
598 |
if( function.parameters().size()!=1 ) {
|
|
598 |
if (function.parameters().size() != 1) {
|
|
599 | 599 |
return; |
600 |
}
|
|
600 |
} |
|
601 | 601 |
ExpressionBuilder.Value arg = function.parameters().get(0); |
602 |
if( !(arg instanceof ExpressionBuilder.Constant) ) {
|
|
602 |
if (!(arg instanceof ExpressionBuilder.Constant)) {
|
|
603 | 603 |
return; |
604 | 604 |
} |
605 | 605 |
Object arg_value = ((ExpressionBuilder.Constant) arg).value(); |
606 |
if( !(arg_value instanceof CharSequence) ) {
|
|
606 |
if (!(arg_value instanceof CharSequence)) {
|
|
607 | 607 |
return; |
608 | 608 |
} |
609 | 609 |
String foreing_value_arg = arg_value.toString(); |
610 | 610 |
String[] foreingNameParts = StringUtils.split(foreing_value_arg, "[.]"); |
611 |
if( foreingNameParts.length!=2 ) {
|
|
611 |
if (foreingNameParts.length != 2) {
|
|
612 | 612 |
// De momento solo tratamos joins entre dos tablas. |
613 | 613 |
return; |
614 | 614 |
} |
615 | 615 |
String columnNameLocal = foreingNameParts[0]; |
616 | 616 |
String columnNameForeing = foreingNameParts[1]; |
617 | 617 |
FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal); |
618 |
if( !attr.isForeingKey() ) {
|
|
618 |
if (!attr.isForeingKey()) {
|
|
619 | 619 |
// Uhm... si el argumento no referencia a un campo que es |
620 | 620 |
// clave ajena no lo procesamos. |
621 | 621 |
// ? Deberiamos lanzar un error ? |
... | ... | |
626 | 626 |
ForeingKey foreingKey = attr.getForeingKey(); |
627 | 627 |
SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder() |
628 | 628 |
.database(table.getDatabase()) |
629 |
.schema(table.getSchema())
|
|
629 |
.schema(table.getSchema()) |
|
630 | 630 |
.name(foreingKey.getTableName()); |
631 | 631 |
// Reemplzaremos la funcion FOREING_VALUE, por el acceso al campo |
632 | 632 |
// que toca de la tabla a la que referencia la clave ajena. |
633 | 633 |
ExpressionBuilder.Variable function_replacement = sqlbuilder.column(foreingTable, columnNameForeing); |
634 | 634 |
value_replacements.add( |
635 |
new ExpressionBuilder.Value[] {
|
|
636 |
function, |
|
637 |
function_replacement |
|
638 |
} |
|
635 |
new ExpressionBuilder.Value[]{
|
|
636 |
function,
|
|
637 |
function_replacement
|
|
638 |
}
|
|
639 | 639 |
); |
640 | 640 |
foreing_value_args.add(foreing_value_arg); |
641 | 641 |
} |
642 | 642 |
}, null); |
643 |
|
|
643 |
|
|
644 | 644 |
// Si no habia ningun llamada a la funcion FOREING_VALUE, no hay que |
645 | 645 |
// hacer nada. |
646 |
if( foreing_value_args.isEmpty() ) {
|
|
646 |
if (foreing_value_args.isEmpty()) {
|
|
647 | 647 |
return null; |
648 | 648 |
} |
649 | 649 |
|
... | ... | |
653 | 653 |
// Para las columnas que puedan dar conflicto se prepara un reemplazo |
654 | 654 |
// de estas que tenga el nombre de tabla. |
655 | 655 |
for (ExpressionBuilder.Variable variable : sqlbuilder.variables()) { |
656 |
if( variable instanceof SQLBuilderBase.ColumnBase ) {
|
|
656 |
if (variable instanceof SQLBuilderBase.ColumnBase) {
|
|
657 | 657 |
continue; |
658 | 658 |
} |
659 | 659 |
for (String foreingName : foreing_value_args) { |
660 | 660 |
String[] foreingNameParts = foreingName.split("[.]"); |
661 |
if( foreingNameParts.length != 2) {
|
|
661 |
if (foreingNameParts.length != 2) {
|
|
662 | 662 |
continue; |
663 | 663 |
} |
664 | 664 |
String columnNameLocal = foreingNameParts[0]; |
665 | 665 |
String columnNameForeing = foreingNameParts[1]; |
666 |
if( !StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing) ) { |
|
667 |
continue; |
|
666 |
if (StringUtils.equalsIgnoreCase(variable.name(), columnNameForeing) |
|
667 |
|| StringUtils.equalsIgnoreCase(variable.name(), columnNameLocal)) { |
|
668 |
ExpressionBuilder.Variable variable_replacement = sqlbuilder.column( |
|
669 |
table, |
|
670 |
variable.name() |
|
671 |
); |
|
672 |
value_replacements.add( |
|
673 |
new ExpressionBuilder.Value[]{ |
|
674 |
variable, |
|
675 |
variable_replacement |
|
676 |
} |
|
677 |
); |
|
668 | 678 |
} |
669 |
ExpressionBuilder.Variable variable_replacement = sqlbuilder.column( |
|
670 |
table, |
|
671 |
variable.name() |
|
672 |
); |
|
673 |
value_replacements.add( |
|
674 |
new ExpressionBuilder.Value[] { |
|
675 |
variable, |
|
676 |
variable_replacement |
|
677 |
} |
|
678 |
); |
|
679 | 679 |
} |
680 | 680 |
} |
681 |
|
|
681 |
|
|
682 | 682 |
// Realizamos los reemplazos calculados previamente (value_replacements). |
683 | 683 |
for (ExpressionBuilder.Value[] replaceValue : value_replacements) { |
684 | 684 |
ExpressionBuilder.Value target = replaceValue[0]; |
685 | 685 |
ExpressionBuilder.Value replacement = replaceValue[1]; |
686 |
sqlbuilder.select().replace(target, replacement);
|
|
686 |
sqlbuilder.select().replace(target, replacement); |
|
687 | 687 |
} |
688 | 688 |
|
689 | 689 |
// A?adimos a la SQL los "LEFT JOIN" que toca para poder acceder |
... | ... | |
692 | 692 |
// como columnas de la SQL para poder acceder a ellos si fuese necesario. |
693 | 693 |
for (String foreingName : foreing_value_args) { |
694 | 694 |
String[] foreingNameParts = foreingName.split("[.]"); |
695 |
if( foreingNameParts.length != 2) {
|
|
695 |
if (foreingNameParts.length != 2) {
|
|
696 | 696 |
continue; |
697 | 697 |
} |
698 | 698 |
String columnNameLocal = foreingNameParts[0]; |
699 | 699 |
String columnNameForeing = foreingNameParts[1]; |
700 | 700 |
FeatureAttributeDescriptor attr = type.getAttributeDescriptor(columnNameLocal); |
701 |
if( attr.isForeingKey() ) {
|
|
701 |
if (attr.isForeingKey()) {
|
|
702 | 702 |
ForeingKey foreingKey = attr.getForeingKey(); |
703 | 703 |
SQLBuilder.TableNameBuilder foreingTable = sqlbuilder.createTableNameBuilder() |
704 | 704 |
.database(table.getDatabase()) |
705 |
.schema(table.getSchema())
|
|
705 |
.schema(table.getSchema()) |
|
706 | 706 |
.name(foreingKey.getTableName()); |
707 | 707 |
SQLBuilder.TableNameBuilder mainTable = sqlbuilder.createTableNameBuilder() |
708 | 708 |
.database(table.getDatabase()) |
709 |
.schema(table.getSchema())
|
|
709 |
.schema(table.getSchema()) |
|
710 | 710 |
.name(table.getName()); |
711 | 711 |
|
712 | 712 |
sqlbuilder.select().from() |
713 |
.left_join( |
|
714 |
foreingTable, |
|
715 |
expbuilder.eq( |
|
716 |
sqlbuilder.column(mainTable, columnNameLocal), |
|
717 |
sqlbuilder.column(foreingTable,foreingKey.getCodeName())
|
|
718 |
) |
|
719 |
); |
|
720 |
sqlbuilder.select().column().name(foreingTable,columnNameForeing); |
|
713 |
.left_join(
|
|
714 |
foreingTable,
|
|
715 |
expbuilder.eq(
|
|
716 |
sqlbuilder.column(mainTable, columnNameLocal),
|
|
717 |
sqlbuilder.column(foreingTable, foreingKey.getCodeName())
|
|
718 |
)
|
|
719 |
);
|
|
720 |
sqlbuilder.select().column().name(foreingTable, columnNameForeing);
|
|
721 | 721 |
} |
722 | 722 |
} |
723 | 723 |
|
Also available in: Unified diff