AbstractSQL.java

  1. /*
  2.  *    Copyright 2009-2024 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.jdbc;

  17. import java.io.IOException;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import java.util.function.BooleanSupplier;
  23. import java.util.function.Consumer;

  24. /**
  25.  * @author Clinton Begin
  26.  * @author Jeff Butler
  27.  * @author Adam Gent
  28.  * @author Kazuki Shimizu
  29.  */
  30. public abstract class AbstractSQL<T> {

  31.   private static final String AND = ") \nAND (";
  32.   private static final String OR = ") \nOR (";

  33.   private final SQLStatement sql = new SQLStatement();

  34.   public abstract T getSelf();

  35.   public T UPDATE(String table) {
  36.     sql().statementType = SQLStatement.StatementType.UPDATE;
  37.     sql().tables.add(table);
  38.     return getSelf();
  39.   }

  40.   public T SET(String sets) {
  41.     sql().sets.add(sets);
  42.     return getSelf();
  43.   }

  44.   /**
  45.    * Sets the.
  46.    *
  47.    * @param sets
  48.    *          the sets
  49.    *
  50.    * @return the t
  51.    *
  52.    * @since 3.4.2
  53.    */
  54.   public T SET(String... sets) {
  55.     sql().sets.addAll(Arrays.asList(sets));
  56.     return getSelf();
  57.   }

  58.   public T INSERT_INTO(String tableName) {
  59.     sql().statementType = SQLStatement.StatementType.INSERT;
  60.     sql().tables.add(tableName);
  61.     return getSelf();
  62.   }

  63.   public T VALUES(String columns, String values) {
  64.     INTO_COLUMNS(columns);
  65.     INTO_VALUES(values);
  66.     return getSelf();
  67.   }

  68.   /**
  69.    * Into columns.
  70.    *
  71.    * @param columns
  72.    *          the columns
  73.    *
  74.    * @return the t
  75.    *
  76.    * @since 3.4.2
  77.    */
  78.   public T INTO_COLUMNS(String... columns) {
  79.     sql().columns.addAll(Arrays.asList(columns));
  80.     return getSelf();
  81.   }

  82.   /**
  83.    * Into values.
  84.    *
  85.    * @param values
  86.    *          the values
  87.    *
  88.    * @return the t
  89.    *
  90.    * @since 3.4.2
  91.    */
  92.   public T INTO_VALUES(String... values) {
  93.     List<String> list = sql().valuesList.get(sql().valuesList.size() - 1);
  94.     Collections.addAll(list, values);
  95.     return getSelf();
  96.   }

  97.   public T SELECT(String columns) {
  98.     sql().statementType = SQLStatement.StatementType.SELECT;
  99.     sql().select.add(columns);
  100.     return getSelf();
  101.   }

  102.   /**
  103.    * Select.
  104.    *
  105.    * @param columns
  106.    *          the columns
  107.    *
  108.    * @return the t
  109.    *
  110.    * @since 3.4.2
  111.    */
  112.   public T SELECT(String... columns) {
  113.     sql().statementType = SQLStatement.StatementType.SELECT;
  114.     sql().select.addAll(Arrays.asList(columns));
  115.     return getSelf();
  116.   }

  117.   public T SELECT_DISTINCT(String columns) {
  118.     sql().distinct = true;
  119.     SELECT(columns);
  120.     return getSelf();
  121.   }

  122.   /**
  123.    * Select distinct.
  124.    *
  125.    * @param columns
  126.    *          the columns
  127.    *
  128.    * @return the t
  129.    *
  130.    * @since 3.4.2
  131.    */
  132.   public T SELECT_DISTINCT(String... columns) {
  133.     sql().distinct = true;
  134.     SELECT(columns);
  135.     return getSelf();
  136.   }

  137.   public T DELETE_FROM(String table) {
  138.     sql().statementType = SQLStatement.StatementType.DELETE;
  139.     sql().tables.add(table);
  140.     return getSelf();
  141.   }

  142.   public T FROM(String table) {
  143.     sql().tables.add(table);
  144.     return getSelf();
  145.   }

  146.   /**
  147.    * From.
  148.    *
  149.    * @param tables
  150.    *          the tables
  151.    *
  152.    * @return the t
  153.    *
  154.    * @since 3.4.2
  155.    */
  156.   public T FROM(String... tables) {
  157.     sql().tables.addAll(Arrays.asList(tables));
  158.     return getSelf();
  159.   }

  160.   public T JOIN(String join) {
  161.     sql().join.add(join);
  162.     return getSelf();
  163.   }

  164.   /**
  165.    * Join.
  166.    *
  167.    * @param joins
  168.    *          the joins
  169.    *
  170.    * @return the t
  171.    *
  172.    * @since 3.4.2
  173.    */
  174.   public T JOIN(String... joins) {
  175.     sql().join.addAll(Arrays.asList(joins));
  176.     return getSelf();
  177.   }

  178.   public T INNER_JOIN(String join) {
  179.     sql().innerJoin.add(join);
  180.     return getSelf();
  181.   }

  182.   /**
  183.    * Inner join.
  184.    *
  185.    * @param joins
  186.    *          the joins
  187.    *
  188.    * @return the t
  189.    *
  190.    * @since 3.4.2
  191.    */
  192.   public T INNER_JOIN(String... joins) {
  193.     sql().innerJoin.addAll(Arrays.asList(joins));
  194.     return getSelf();
  195.   }

  196.   public T LEFT_OUTER_JOIN(String join) {
  197.     sql().leftOuterJoin.add(join);
  198.     return getSelf();
  199.   }

  200.   /**
  201.    * Left outer join.
  202.    *
  203.    * @param joins
  204.    *          the joins
  205.    *
  206.    * @return the t
  207.    *
  208.    * @since 3.4.2
  209.    */
  210.   public T LEFT_OUTER_JOIN(String... joins) {
  211.     sql().leftOuterJoin.addAll(Arrays.asList(joins));
  212.     return getSelf();
  213.   }

  214.   public T RIGHT_OUTER_JOIN(String join) {
  215.     sql().rightOuterJoin.add(join);
  216.     return getSelf();
  217.   }

  218.   /**
  219.    * Right outer join.
  220.    *
  221.    * @param joins
  222.    *          the joins
  223.    *
  224.    * @return the t
  225.    *
  226.    * @since 3.4.2
  227.    */
  228.   public T RIGHT_OUTER_JOIN(String... joins) {
  229.     sql().rightOuterJoin.addAll(Arrays.asList(joins));
  230.     return getSelf();
  231.   }

  232.   public T OUTER_JOIN(String join) {
  233.     sql().outerJoin.add(join);
  234.     return getSelf();
  235.   }

  236.   /**
  237.    * Outer join.
  238.    *
  239.    * @param joins
  240.    *          the joins
  241.    *
  242.    * @return the t
  243.    *
  244.    * @since 3.4.2
  245.    */
  246.   public T OUTER_JOIN(String... joins) {
  247.     sql().outerJoin.addAll(Arrays.asList(joins));
  248.     return getSelf();
  249.   }

  250.   public T WHERE(String conditions) {
  251.     sql().where.add(conditions);
  252.     sql().lastList = sql().where;
  253.     return getSelf();
  254.   }

  255.   /**
  256.    * Where.
  257.    *
  258.    * @param conditions
  259.    *          the conditions
  260.    *
  261.    * @return the t
  262.    *
  263.    * @since 3.4.2
  264.    */
  265.   public T WHERE(String... conditions) {
  266.     sql().where.addAll(Arrays.asList(conditions));
  267.     sql().lastList = sql().where;
  268.     return getSelf();
  269.   }

  270.   public T OR() {
  271.     sql().lastList.add(OR);
  272.     return getSelf();
  273.   }

  274.   public T AND() {
  275.     sql().lastList.add(AND);
  276.     return getSelf();
  277.   }

  278.   public T GROUP_BY(String columns) {
  279.     sql().groupBy.add(columns);
  280.     return getSelf();
  281.   }

  282.   /**
  283.    * Group by.
  284.    *
  285.    * @param columns
  286.    *          the columns
  287.    *
  288.    * @return the t
  289.    *
  290.    * @since 3.4.2
  291.    */
  292.   public T GROUP_BY(String... columns) {
  293.     sql().groupBy.addAll(Arrays.asList(columns));
  294.     return getSelf();
  295.   }

  296.   public T HAVING(String conditions) {
  297.     sql().having.add(conditions);
  298.     sql().lastList = sql().having;
  299.     return getSelf();
  300.   }

  301.   /**
  302.    * Having.
  303.    *
  304.    * @param conditions
  305.    *          the conditions
  306.    *
  307.    * @return the t
  308.    *
  309.    * @since 3.4.2
  310.    */
  311.   public T HAVING(String... conditions) {
  312.     sql().having.addAll(Arrays.asList(conditions));
  313.     sql().lastList = sql().having;
  314.     return getSelf();
  315.   }

  316.   public T ORDER_BY(String columns) {
  317.     sql().orderBy.add(columns);
  318.     return getSelf();
  319.   }

  320.   /**
  321.    * Order by.
  322.    *
  323.    * @param columns
  324.    *          the columns
  325.    *
  326.    * @return the t
  327.    *
  328.    * @since 3.4.2
  329.    */
  330.   public T ORDER_BY(String... columns) {
  331.     sql().orderBy.addAll(Arrays.asList(columns));
  332.     return getSelf();
  333.   }

  334.   /**
  335.    * Set the limit variable string(e.g. {@code "#{limit}"}).
  336.    *
  337.    * @param variable
  338.    *          a limit variable string
  339.    *
  340.    * @return a self instance
  341.    *
  342.    * @see #OFFSET(String)
  343.    *
  344.    * @since 3.5.2
  345.    */
  346.   public T LIMIT(String variable) {
  347.     sql().limit = variable;
  348.     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.OFFSET_LIMIT;
  349.     return getSelf();
  350.   }

  351.   /**
  352.    * Set the limit value.
  353.    *
  354.    * @param value
  355.    *          an offset value
  356.    *
  357.    * @return a self instance
  358.    *
  359.    * @see #OFFSET(long)
  360.    *
  361.    * @since 3.5.2
  362.    */
  363.   public T LIMIT(int value) {
  364.     return LIMIT(String.valueOf(value));
  365.   }

  366.   /**
  367.    * Set the offset variable string(e.g. {@code "#{offset}"}).
  368.    *
  369.    * @param variable
  370.    *          a offset variable string
  371.    *
  372.    * @return a self instance
  373.    *
  374.    * @see #LIMIT(String)
  375.    *
  376.    * @since 3.5.2
  377.    */
  378.   public T OFFSET(String variable) {
  379.     sql().offset = variable;
  380.     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.OFFSET_LIMIT;
  381.     return getSelf();
  382.   }

  383.   /**
  384.    * Set the offset value.
  385.    *
  386.    * @param value
  387.    *          an offset value
  388.    *
  389.    * @return a self instance
  390.    *
  391.    * @see #LIMIT(int)
  392.    *
  393.    * @since 3.5.2
  394.    */
  395.   public T OFFSET(long value) {
  396.     return OFFSET(String.valueOf(value));
  397.   }

  398.   /**
  399.    * Set the fetch first rows variable string(e.g. {@code "#{fetchFirstRows}"}).
  400.    *
  401.    * @param variable
  402.    *          a fetch first rows variable string
  403.    *
  404.    * @return a self instance
  405.    *
  406.    * @see #OFFSET_ROWS(String)
  407.    *
  408.    * @since 3.5.2
  409.    */
  410.   public T FETCH_FIRST_ROWS_ONLY(String variable) {
  411.     sql().limit = variable;
  412.     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.ISO;
  413.     return getSelf();
  414.   }

  415.   /**
  416.    * Set the fetch first rows value.
  417.    *
  418.    * @param value
  419.    *          a fetch first rows value
  420.    *
  421.    * @return a self instance
  422.    *
  423.    * @see #OFFSET_ROWS(long)
  424.    *
  425.    * @since 3.5.2
  426.    */
  427.   public T FETCH_FIRST_ROWS_ONLY(int value) {
  428.     return FETCH_FIRST_ROWS_ONLY(String.valueOf(value));
  429.   }

  430.   /**
  431.    * Set the offset rows variable string(e.g. {@code "#{offset}"}).
  432.    *
  433.    * @param variable
  434.    *          a offset rows variable string
  435.    *
  436.    * @return a self instance
  437.    *
  438.    * @see #FETCH_FIRST_ROWS_ONLY(String)
  439.    *
  440.    * @since 3.5.2
  441.    */
  442.   public T OFFSET_ROWS(String variable) {
  443.     sql().offset = variable;
  444.     sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.ISO;
  445.     return getSelf();
  446.   }

  447.   /**
  448.    * Set the offset rows value.
  449.    *
  450.    * @param value
  451.    *          an offset rows value
  452.    *
  453.    * @return a self instance
  454.    *
  455.    * @see #FETCH_FIRST_ROWS_ONLY(int)
  456.    *
  457.    * @since 3.5.2
  458.    */
  459.   public T OFFSET_ROWS(long value) {
  460.     return OFFSET_ROWS(String.valueOf(value));
  461.   }

  462.   /**
  463.    * used to add a new inserted row while do multi-row insert.
  464.    *
  465.    * @return the t
  466.    *
  467.    * @since 3.5.2
  468.    */
  469.   public T ADD_ROW() {
  470.     sql().valuesList.add(new ArrayList<>());
  471.     return getSelf();
  472.   }

  473.   private SQLStatement sql() {
  474.     return sql;
  475.   }

  476.   public <A extends Appendable> A usingAppender(A a) {
  477.     sql().sql(a);
  478.     return a;
  479.   }

  480.   /**
  481.    * Apply sql phrases that provide by SQL consumer if condition is matches.
  482.    *
  483.    * @param applyCondition
  484.    *          if {@code true} apply sql phrases
  485.    * @param sqlConsumer
  486.    *          a consumer that append sql phrase to SQL instance
  487.    *
  488.    * @return a self instance
  489.    *
  490.    * @see #applyIf(BooleanSupplier, Consumer)
  491.    *
  492.    * @since 3.5.15
  493.    */
  494.   public T applyIf(boolean applyCondition, Consumer<T> sqlConsumer) {
  495.     T self = getSelf();
  496.     if (applyCondition) {
  497.       sqlConsumer.accept(self);
  498.     }
  499.     return self;
  500.   }

  501.   /**
  502.    * Apply sql phrases that provide by SQL consumer if condition is matches.
  503.    *
  504.    * @param applyConditionSupplier
  505.    *          if supplier return {@code true} apply sql phrases
  506.    * @param sqlConsumer
  507.    *          a consumer that append sql phrase to SQL instance
  508.    *
  509.    * @return a self instance
  510.    *
  511.    * @see #applyIf(boolean, Consumer)
  512.    *
  513.    * @since 3.5.15
  514.    */
  515.   public T applyIf(BooleanSupplier applyConditionSupplier, Consumer<T> sqlConsumer) {
  516.     return applyIf(applyConditionSupplier.getAsBoolean(), sqlConsumer);
  517.   }

  518.   /**
  519.    * Apply sql phrases that provide by SQL consumer for iterable.
  520.    *
  521.    * @param iterable
  522.    *          an iterable
  523.    * @param forEachSqlConsumer
  524.    *          a consumer that append sql phrase to SQL instance
  525.    *
  526.    * @return a self instance
  527.    *
  528.    * @param <E>
  529.    *          element type of iterable
  530.    *
  531.    * @since 3.5.15
  532.    */
  533.   public <E> T applyForEach(Iterable<E> iterable, ForEachConsumer<T, E> forEachSqlConsumer) {
  534.     T self = getSelf();
  535.     int elementIndex = 0;
  536.     for (E element : iterable) {
  537.       forEachSqlConsumer.accept(self, element, elementIndex);
  538.       elementIndex++;
  539.     }
  540.     return self;
  541.   }

  542.   @Override
  543.   public String toString() {
  544.     StringBuilder sb = new StringBuilder();
  545.     sql().sql(sb);
  546.     return sb.toString();
  547.   }

  548.   private static class SafeAppendable {
  549.     private final Appendable appendable;
  550.     private boolean empty = true;

  551.     public SafeAppendable(Appendable a) {
  552.       this.appendable = a;
  553.     }

  554.     public SafeAppendable append(CharSequence s) {
  555.       try {
  556.         if (empty && s.length() > 0) {
  557.           empty = false;
  558.         }
  559.         appendable.append(s);
  560.       } catch (IOException e) {
  561.         throw new RuntimeException(e);
  562.       }
  563.       return this;
  564.     }

  565.     public boolean isEmpty() {
  566.       return empty;
  567.     }

  568.   }

  569.   private static class SQLStatement {

  570.     public enum StatementType {

  571.       DELETE,

  572.       INSERT,

  573.       SELECT,

  574.       UPDATE

  575.     }

  576.     private enum LimitingRowsStrategy {
  577.       NOP {
  578.         @Override
  579.         protected void appendClause(SafeAppendable builder, String offset, String limit) {
  580.           // NOP
  581.         }
  582.       },
  583.       ISO {
  584.         @Override
  585.         protected void appendClause(SafeAppendable builder, String offset, String limit) {
  586.           if (offset != null) {
  587.             builder.append(" OFFSET ").append(offset).append(" ROWS");
  588.           }
  589.           if (limit != null) {
  590.             builder.append(" FETCH FIRST ").append(limit).append(" ROWS ONLY");
  591.           }
  592.         }
  593.       },
  594.       OFFSET_LIMIT {
  595.         @Override
  596.         protected void appendClause(SafeAppendable builder, String offset, String limit) {
  597.           if (limit != null) {
  598.             builder.append(" LIMIT ").append(limit);
  599.           }
  600.           if (offset != null) {
  601.             builder.append(" OFFSET ").append(offset);
  602.           }
  603.         }
  604.       };

  605.       protected abstract void appendClause(SafeAppendable builder, String offset, String limit);

  606.     }

  607.     StatementType statementType;
  608.     List<String> sets = new ArrayList<>();
  609.     List<String> select = new ArrayList<>();
  610.     List<String> tables = new ArrayList<>();
  611.     List<String> join = new ArrayList<>();
  612.     List<String> innerJoin = new ArrayList<>();
  613.     List<String> outerJoin = new ArrayList<>();
  614.     List<String> leftOuterJoin = new ArrayList<>();
  615.     List<String> rightOuterJoin = new ArrayList<>();
  616.     List<String> where = new ArrayList<>();
  617.     List<String> having = new ArrayList<>();
  618.     List<String> groupBy = new ArrayList<>();
  619.     List<String> orderBy = new ArrayList<>();
  620.     List<String> lastList = new ArrayList<>();
  621.     List<String> columns = new ArrayList<>();
  622.     List<List<String>> valuesList = new ArrayList<>();
  623.     boolean distinct;
  624.     String offset;
  625.     String limit;
  626.     LimitingRowsStrategy limitingRowsStrategy = LimitingRowsStrategy.NOP;

  627.     public SQLStatement() {
  628.       // Prevent Synthetic Access
  629.       valuesList.add(new ArrayList<>());
  630.     }

  631.     private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close,
  632.         String conjunction) {
  633.       if (!parts.isEmpty()) {
  634.         if (!builder.isEmpty()) {
  635.           builder.append("\n");
  636.         }
  637.         builder.append(keyword);
  638.         builder.append(" ");
  639.         builder.append(open);
  640.         String last = "________";
  641.         for (int i = 0, n = parts.size(); i < n; i++) {
  642.           String part = parts.get(i);
  643.           if (i > 0 && !AND.equals(part) && !OR.equals(part) && !AND.equals(last) && !OR.equals(last)) {
  644.             builder.append(conjunction);
  645.           }
  646.           builder.append(part);
  647.           last = part;
  648.         }
  649.         builder.append(close);
  650.       }
  651.     }

  652.     private String selectSQL(SafeAppendable builder) {
  653.       if (distinct) {
  654.         sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
  655.       } else {
  656.         sqlClause(builder, "SELECT", select, "", "", ", ");
  657.       }

  658.       sqlClause(builder, "FROM", tables, "", "", ", ");
  659.       joins(builder);
  660.       sqlClause(builder, "WHERE", where, "(", ")", " AND ");
  661.       sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
  662.       sqlClause(builder, "HAVING", having, "(", ")", " AND ");
  663.       sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
  664.       limitingRowsStrategy.appendClause(builder, offset, limit);
  665.       return builder.toString();
  666.     }

  667.     private void joins(SafeAppendable builder) {
  668.       sqlClause(builder, "JOIN", join, "", "", "\nJOIN ");
  669.       sqlClause(builder, "INNER JOIN", innerJoin, "", "", "\nINNER JOIN ");
  670.       sqlClause(builder, "OUTER JOIN", outerJoin, "", "", "\nOUTER JOIN ");
  671.       sqlClause(builder, "LEFT OUTER JOIN", leftOuterJoin, "", "", "\nLEFT OUTER JOIN ");
  672.       sqlClause(builder, "RIGHT OUTER JOIN", rightOuterJoin, "", "", "\nRIGHT OUTER JOIN ");
  673.     }

  674.     private String insertSQL(SafeAppendable builder) {
  675.       sqlClause(builder, "INSERT INTO", tables, "", "", "");
  676.       sqlClause(builder, "", columns, "(", ")", ", ");
  677.       for (int i = 0; i < valuesList.size(); i++) {
  678.         sqlClause(builder, i > 0 ? "," : "VALUES", valuesList.get(i), "(", ")", ", ");
  679.       }
  680.       return builder.toString();
  681.     }

  682.     private String deleteSQL(SafeAppendable builder) {
  683.       sqlClause(builder, "DELETE FROM", tables, "", "", "");
  684.       sqlClause(builder, "WHERE", where, "(", ")", " AND ");
  685.       limitingRowsStrategy.appendClause(builder, null, limit);
  686.       return builder.toString();
  687.     }

  688.     private String updateSQL(SafeAppendable builder) {
  689.       sqlClause(builder, "UPDATE", tables, "", "", "");
  690.       joins(builder);
  691.       sqlClause(builder, "SET", sets, "", "", ", ");
  692.       sqlClause(builder, "WHERE", where, "(", ")", " AND ");
  693.       limitingRowsStrategy.appendClause(builder, null, limit);
  694.       return builder.toString();
  695.     }

  696.     public String sql(Appendable a) {
  697.       SafeAppendable builder = new SafeAppendable(a);
  698.       if (statementType == null) {
  699.         return null;
  700.       }

  701.       String answer;

  702.       switch (statementType) {
  703.         case DELETE:
  704.           answer = deleteSQL(builder);
  705.           break;

  706.         case INSERT:
  707.           answer = insertSQL(builder);
  708.           break;

  709.         case SELECT:
  710.           answer = selectSQL(builder);
  711.           break;

  712.         case UPDATE:
  713.           answer = updateSQL(builder);
  714.           break;

  715.         default:
  716.           answer = null;
  717.       }

  718.       return answer;
  719.     }
  720.   }

  721.   /**
  722.    * Consumer for 'forEach' operation.
  723.    *
  724.    * @param <T>
  725.    *          SQL type
  726.    * @param <E>
  727.    *          Element type of iterable
  728.    *
  729.    * @since 3.5.15
  730.    */
  731.   public interface ForEachConsumer<T, E> {

  732.     /**
  733.      * Accept an iterable element with index.
  734.      *
  735.      * @param sql
  736.      *          SQL instance
  737.      * @param element
  738.      *          an iterable element
  739.      * @param elementIndex
  740.      *          an element index
  741.      */
  742.     void accept(T sql, E element, int elementIndex);

  743.   }

  744. }