|
30 | 30 | import java.lang.reflect.Method;
|
31 | 31 | import java.lang.reflect.Proxy;
|
32 | 32 | import java.sql.SQLException;
|
| 33 | +import java.util.ArrayList; |
| 34 | +import java.util.Arrays; |
33 | 35 | import java.util.Collections;
|
34 | 36 | import java.util.LinkedHashMap;
|
35 | 37 | import java.util.List;
|
|
47 | 49 | import org.reactivestreams.Publisher;
|
48 | 50 | import org.springframework.dao.DataAccessException;
|
49 | 51 | import org.springframework.dao.UncategorizedDataAccessException;
|
| 52 | +import org.springframework.data.domain.Pageable; |
| 53 | +import org.springframework.data.domain.Sort; |
| 54 | +import org.springframework.data.domain.Sort.NullHandling; |
| 55 | +import org.springframework.data.domain.Sort.Order; |
50 | 56 | import org.springframework.data.jdbc.core.function.connectionfactory.ConnectionProxy;
|
51 | 57 | import org.springframework.data.util.Pair;
|
52 | 58 | import org.springframework.jdbc.core.SqlProvider;
|
53 | 59 | import org.springframework.jdbc.support.SQLExceptionTranslator;
|
54 | 60 | import org.springframework.lang.Nullable;
|
55 | 61 | import org.springframework.util.Assert;
|
| 62 | +import org.springframework.util.StringUtils; |
56 | 63 |
|
57 | 64 | /**
|
58 | 65 | * Default implementation of {@link DatabaseClient}.
|
@@ -93,7 +100,7 @@ public SqlSpec execute() {
|
93 | 100 |
|
94 | 101 | @Override
|
95 | 102 | public SelectFromSpec select() {
|
96 |
| - throw new UnsupportedOperationException("Implement me"); |
| 103 | + return new DefaultSelectFromSpec(); |
97 | 104 | }
|
98 | 105 |
|
99 | 106 | @Override
|
@@ -254,7 +261,8 @@ public GenericExecuteSpec sql(Supplier<String> sqlSupplier) {
|
254 | 261 | }
|
255 | 262 |
|
256 | 263 | /**
|
257 |
| - * Default {@link org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec} implementation. |
| 264 | + * Base class for {@link org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec} |
| 265 | + * implementations. |
258 | 266 | */
|
259 | 267 | @RequiredArgsConstructor
|
260 | 268 | private class GenericExecuteSpecSupport {
|
@@ -481,6 +489,295 @@ protected DefaultTypedGenericExecuteSpec<T> createInstance(Map<Integer, Optional
|
481 | 489 | }
|
482 | 490 | }
|
483 | 491 |
|
| 492 | + /** |
| 493 | + * Default {@link org.springframework.data.jdbc.core.function.DatabaseClient.SelectFromSpec} implementation. |
| 494 | + */ |
| 495 | + class DefaultSelectFromSpec implements SelectFromSpec { |
| 496 | + |
| 497 | + @Override |
| 498 | + public GenericSelectSpec from(String table) { |
| 499 | + return new DefaultGenericSelectSpec(table); |
| 500 | + } |
| 501 | + |
| 502 | + @Override |
| 503 | + public <T> TypedSelectSpec<T> from(Class<T> table) { |
| 504 | + return new DefaultTypedSelectSpec<>(table); |
| 505 | + } |
| 506 | + } |
| 507 | + |
| 508 | + /** |
| 509 | + * Base class for {@link org.springframework.data.jdbc.core.function.DatabaseClient.GenericExecuteSpec} |
| 510 | + * implementations. |
| 511 | + */ |
| 512 | + @RequiredArgsConstructor |
| 513 | + private abstract class DefaultSelectSpecSupport { |
| 514 | + |
| 515 | + final String table; |
| 516 | + final List<String> projectedFields; |
| 517 | + final Sort sort; |
| 518 | + final Pageable page; |
| 519 | + |
| 520 | + DefaultSelectSpecSupport(String table) { |
| 521 | + |
| 522 | + Assert.hasText(table, "Table name must not be null!"); |
| 523 | + |
| 524 | + this.table = table; |
| 525 | + this.projectedFields = Collections.emptyList(); |
| 526 | + this.sort = Sort.unsorted(); |
| 527 | + this.page = Pageable.unpaged(); |
| 528 | + } |
| 529 | + |
| 530 | + public DefaultSelectSpecSupport project(String... selectedFields) { |
| 531 | + Assert.notNull(selectedFields, "Projection fields must not be null!"); |
| 532 | + |
| 533 | + List<String> projectedFields = new ArrayList<>(this.projectedFields.size() + selectedFields.length); |
| 534 | + projectedFields.addAll(this.projectedFields); |
| 535 | + projectedFields.addAll(Arrays.asList(selectedFields)); |
| 536 | + |
| 537 | + return createInstance(table, projectedFields, sort, page); |
| 538 | + } |
| 539 | + |
| 540 | + public DefaultSelectSpecSupport orderBy(Sort sort) { |
| 541 | + |
| 542 | + Assert.notNull(sort, "Sort must not be null!"); |
| 543 | + |
| 544 | + return createInstance(table, projectedFields, sort, page); |
| 545 | + } |
| 546 | + |
| 547 | + public DefaultSelectSpecSupport page(Pageable page) { |
| 548 | + |
| 549 | + Assert.notNull(page, "Pageable must not be null!"); |
| 550 | + |
| 551 | + return createInstance(table, projectedFields, sort, page); |
| 552 | + } |
| 553 | + |
| 554 | + StringBuilder getLimitOffset(Pageable pageable) { |
| 555 | + return new StringBuilder().append("LIMIT").append(' ').append(page.getPageSize()) // |
| 556 | + .append(' ').append("OFFSET").append(' ').append(page.getOffset()); |
| 557 | + } |
| 558 | + |
| 559 | + StringBuilder getSortClause(Sort sort) { |
| 560 | + |
| 561 | + StringBuilder sortClause = new StringBuilder(); |
| 562 | + |
| 563 | + for (Order order : sort) { |
| 564 | + |
| 565 | + if (sortClause.length() != 0) { |
| 566 | + sortClause.append(',').append(' '); |
| 567 | + } |
| 568 | + |
| 569 | + sortClause.append(order.getProperty()).append(' ').append(order.getDirection().isAscending() ? "ASC" : "DESC"); |
| 570 | + |
| 571 | + if (order.getNullHandling() == NullHandling.NULLS_FIRST) { |
| 572 | + sortClause.append(' ').append("NULLS FIRST"); |
| 573 | + } |
| 574 | + |
| 575 | + if (order.getNullHandling() == NullHandling.NULLS_LAST) { |
| 576 | + sortClause.append(' ').append("NULLS LAST"); |
| 577 | + } |
| 578 | + } |
| 579 | + return sortClause; |
| 580 | + } |
| 581 | + |
| 582 | + <R> SqlResult<R> execute(String sql, BiFunction<Row, RowMetadata, R> mappingFunction) { |
| 583 | + |
| 584 | + Function<Connection, Statement> selectFunction = it -> { |
| 585 | + |
| 586 | + if (logger.isDebugEnabled()) { |
| 587 | + logger.debug("Executing SQL statement [" + sql + "]"); |
| 588 | + } |
| 589 | + |
| 590 | + return it.createStatement(sql); |
| 591 | + }; |
| 592 | + |
| 593 | + Function<Connection, Flux<Result>> resultFunction = it -> Flux.from(selectFunction.apply(it).execute()); |
| 594 | + |
| 595 | + return new DefaultSqlResult<>(DefaultDatabaseClient.this, // |
| 596 | + sql, // |
| 597 | + resultFunction, // |
| 598 | + it -> Mono.error(new UnsupportedOperationException("Not available for SELECT")), // |
| 599 | + mappingFunction); |
| 600 | + } |
| 601 | + |
| 602 | + protected abstract DefaultSelectSpecSupport createInstance(String table, List<String> projectedFields, Sort sort, |
| 603 | + Pageable page); |
| 604 | + } |
| 605 | + |
| 606 | + private class DefaultGenericSelectSpec extends DefaultSelectSpecSupport implements GenericSelectSpec { |
| 607 | + |
| 608 | + public DefaultGenericSelectSpec(String table, List<String> projectedFields, Sort sort, Pageable page) { |
| 609 | + super(table, projectedFields, sort, page); |
| 610 | + } |
| 611 | + |
| 612 | + DefaultGenericSelectSpec(String table) { |
| 613 | + super(table); |
| 614 | + } |
| 615 | + |
| 616 | + @Override |
| 617 | + public <R> TypedSelectSpec<R> as(Class<R> resultType) { |
| 618 | + return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, resultType, |
| 619 | + dataAccessStrategy.getRowMapper(resultType)); |
| 620 | + } |
| 621 | + |
| 622 | + @Override |
| 623 | + public DefaultGenericSelectSpec project(String... selectedFields) { |
| 624 | + return (DefaultGenericSelectSpec) super.project(selectedFields); |
| 625 | + } |
| 626 | + |
| 627 | + @Override |
| 628 | + public DefaultGenericSelectSpec orderBy(Sort sort) { |
| 629 | + return (DefaultGenericSelectSpec) super.orderBy(sort); |
| 630 | + } |
| 631 | + |
| 632 | + @Override |
| 633 | + public DefaultGenericSelectSpec page(Pageable page) { |
| 634 | + return (DefaultGenericSelectSpec) super.page(page); |
| 635 | + } |
| 636 | + |
| 637 | + @Override |
| 638 | + public FetchSpec<Map<String, Object>> fetch() { |
| 639 | + return exchange(ColumnMapRowMapper.INSTANCE); |
| 640 | + } |
| 641 | + |
| 642 | + @Override |
| 643 | + public Mono<SqlResult<Map<String, Object>>> exchange() { |
| 644 | + return Mono.just(exchange(ColumnMapRowMapper.INSTANCE)); |
| 645 | + } |
| 646 | + |
| 647 | + private <R> SqlResult<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunction) { |
| 648 | + |
| 649 | + List<String> projectedFields; |
| 650 | + |
| 651 | + if (this.projectedFields.isEmpty()) { |
| 652 | + projectedFields = Collections.singletonList("*"); |
| 653 | + } else { |
| 654 | + projectedFields = this.projectedFields; |
| 655 | + } |
| 656 | + |
| 657 | + StringBuilder selectBuilder = new StringBuilder(); |
| 658 | + selectBuilder.append("SELECT").append(' ') // |
| 659 | + .append(StringUtils.collectionToDelimitedString(projectedFields, ", ")).append(' ') // |
| 660 | + .append("FROM").append(' ').append(table); |
| 661 | + |
| 662 | + if (sort.isSorted()) { |
| 663 | + selectBuilder.append(' ').append("ORDER BY").append(' ').append(getSortClause(sort)); |
| 664 | + } |
| 665 | + |
| 666 | + if (page.isPaged()) { |
| 667 | + selectBuilder.append(' ').append(getLimitOffset(page)); |
| 668 | + } |
| 669 | + |
| 670 | + return execute(selectBuilder.toString(), mappingFunction); |
| 671 | + } |
| 672 | + |
| 673 | + @Override |
| 674 | + protected DefaultGenericSelectSpec createInstance(String table, List<String> projectedFields, Sort sort, |
| 675 | + Pageable page) { |
| 676 | + return new DefaultGenericSelectSpec(table, projectedFields, sort, page); |
| 677 | + } |
| 678 | + } |
| 679 | + |
| 680 | + /** |
| 681 | + * Default implementation of {@link org.springframework.data.jdbc.core.function.DatabaseClient.TypedInsertSpec}. |
| 682 | + */ |
| 683 | + @SuppressWarnings("unchecked") |
| 684 | + private class DefaultTypedSelectSpec<T> extends DefaultSelectSpecSupport implements TypedSelectSpec<T> { |
| 685 | + |
| 686 | + private final Class<?> typeToRead; |
| 687 | + private final BiFunction<Row, RowMetadata, T> mappingFunction; |
| 688 | + |
| 689 | + DefaultTypedSelectSpec(Class<T> typeToRead) { |
| 690 | + |
| 691 | + super(dataAccessStrategy.getTableName(typeToRead)); |
| 692 | + |
| 693 | + this.typeToRead = typeToRead; |
| 694 | + this.mappingFunction = dataAccessStrategy.getRowMapper(typeToRead); |
| 695 | + } |
| 696 | + |
| 697 | + DefaultTypedSelectSpec(String table, List<String> projectedFields, Sort sort, Pageable page, Class<?> typeToRead, |
| 698 | + BiFunction<Row, RowMetadata, T> mappingFunction) { |
| 699 | + super(table, projectedFields, sort, page); |
| 700 | + this.typeToRead = typeToRead; |
| 701 | + this.mappingFunction = mappingFunction; |
| 702 | + } |
| 703 | + |
| 704 | + @Override |
| 705 | + public <R> TypedSelectSpec<R> as(Class<R> resultType) { |
| 706 | + |
| 707 | + Assert.notNull(resultType, "Result type must not be null!"); |
| 708 | + |
| 709 | + return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, typeToRead, |
| 710 | + dataAccessStrategy.getRowMapper(resultType)); |
| 711 | + } |
| 712 | + |
| 713 | + @Override |
| 714 | + public <R> TypedSelectSpec<R> extract(BiFunction<Row, RowMetadata, R> mappingFunction) { |
| 715 | + |
| 716 | + Assert.notNull(mappingFunction, "Mapping function must not be null!"); |
| 717 | + |
| 718 | + return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, typeToRead, mappingFunction); |
| 719 | + } |
| 720 | + |
| 721 | + @Override |
| 722 | + public DefaultTypedSelectSpec<T> project(String... selectedFields) { |
| 723 | + return (DefaultTypedSelectSpec<T>) super.project(selectedFields); |
| 724 | + } |
| 725 | + |
| 726 | + @Override |
| 727 | + public DefaultTypedSelectSpec<T> orderBy(Sort sort) { |
| 728 | + return (DefaultTypedSelectSpec<T>) super.orderBy(sort); |
| 729 | + } |
| 730 | + |
| 731 | + @Override |
| 732 | + public DefaultTypedSelectSpec<T> page(Pageable page) { |
| 733 | + return (DefaultTypedSelectSpec<T>) super.page(page); |
| 734 | + } |
| 735 | + |
| 736 | + @Override |
| 737 | + public FetchSpec<T> fetch() { |
| 738 | + return exchange(mappingFunction); |
| 739 | + } |
| 740 | + |
| 741 | + @Override |
| 742 | + public Mono<SqlResult<T>> exchange() { |
| 743 | + return Mono.just(exchange(mappingFunction)); |
| 744 | + } |
| 745 | + |
| 746 | + private <R> SqlResult<R> exchange(BiFunction<Row, RowMetadata, R> mappingFunction) { |
| 747 | + |
| 748 | + List<String> projectedFields; |
| 749 | + |
| 750 | + if (this.projectedFields.isEmpty()) { |
| 751 | + projectedFields = dataAccessStrategy.getAllFields(typeToRead); |
| 752 | + } else { |
| 753 | + projectedFields = this.projectedFields; |
| 754 | + } |
| 755 | + |
| 756 | + StringBuilder selectBuilder = new StringBuilder(); |
| 757 | + selectBuilder.append("SELECT").append(' ') // |
| 758 | + .append(StringUtils.collectionToDelimitedString(projectedFields, ", ")).append(' ') // |
| 759 | + .append("FROM").append(' ').append(table); |
| 760 | + |
| 761 | + if (sort.isSorted()) { |
| 762 | + |
| 763 | + Sort mappedSort = dataAccessStrategy.getMappedSort(typeToRead, sort); |
| 764 | + selectBuilder.append(' ').append("ORDER BY").append(' ').append(getSortClause(mappedSort)); |
| 765 | + } |
| 766 | + |
| 767 | + if (page.isPaged()) { |
| 768 | + selectBuilder.append(' ').append(getLimitOffset(page)); |
| 769 | + } |
| 770 | + |
| 771 | + return execute(selectBuilder.toString(), mappingFunction); |
| 772 | + } |
| 773 | + |
| 774 | + @Override |
| 775 | + protected DefaultTypedSelectSpec<T> createInstance(String table, List<String> projectedFields, Sort sort, |
| 776 | + Pageable page) { |
| 777 | + return new DefaultTypedSelectSpec<>(table, projectedFields, sort, page, typeToRead, mappingFunction); |
| 778 | + } |
| 779 | + } |
| 780 | + |
484 | 781 | /**
|
485 | 782 | * Default {@link org.springframework.data.jdbc.core.function.DatabaseClient.InsertIntoSpec} implementation.
|
486 | 783 | */
|
|
0 commit comments