text: #include "tx_shim.h"
  #include "tpcc.h"
  #include <stdio.h>
  #include <string.h>
  #include <time.h>
  #define new(T) malloc(sizeof(T))
  
  //////////////////////
  // Transactions
  //////////////////////
  
  static FILE* fp;
  static FILE* delivery_tx_result_fp;
  
  static inline char* asc_local_time(void)
  {
      time_t* cur_time = new(time_t); time(cur_time);
      return asctime(localtime(cur_time));
  }
  
  void trans_new_order(tx_ctx_t* ctx)
  // enter the new order generated by gen_new_order() through a single database transaction
  {
      // input format:
      //  transaction type, w_id, d_id, c_id, ol_cnt
      //   - (order line 1) ol_i_id, ol_supply_w_id, ol_quantity
      //   ...
      int w_id, d_id, c_id, ol_cnt;
      fscanf(fp, "%d%d%d%d", &w_id, &d_id, &c_id, &ol_cnt);
  
      tx_trans_t* trans = tx_trans_create(ctx);  tx_trans_init(ctx, trans);
      
      warehouse_t* w; Select_warehouse(trans, w_id, &w);
      // The row in the WAREHOUSE table with matching W_ID is selected and
      //  W_TAX, the warehouse tax rate, is retrieved.
      
      district_t* d; Select_district(trans, w_id, d_id, &d);
      // Note that we want to do an update here rather than an insert. But the backend
      //  only provides set() that fulfills both the insert and the update action.
      //  Do we need to add a get() before set() to tell between these two actions???
  
      // The row in the DISTRICT table with matching D_W_ID and D_ID is selected,
      //  D_TAX, the district tax rate, is retrieved, and D_NEXT_O_ID,
      //  the next available order number for the district, is retrieved and incremented by one.
      
      customer_t* c; Select_customer(trans, w_id, d_id, c_id, &c);
      // The row in the CUSTOMER table with matching C_W_ID, C_D_ID, and C_ID is selected
      //  and C_DISCOUNT, the customer's discount rate, C_LAST, the customer's last name,
      //  and C_CREDIT, the customer's credit status, are retrieved.
      
      neworder_t* no = new(neworder_t);
      no->no_w_id = w_id; no->no_d_id = d_id; no->no_o_id = d->d_next_o_id;
      Insert_neworder(trans, no);
  
      order_t* o = new(order_t);
      o->o_w_id = w_id; o->o_d_id = d_id; o->o_id = d->d_next_o_id;
      o->o_c_id = c_id; o->o_all_local = 1; o->o_carrier_id = -1;
      o->o_entry_d = new(struct tm); *o->o_entry_d = cur_local_time();  // ??? can be optimized
      // A new row is inserted into both the NEW-ORDER table and the ORDER table to
      //  reflect the creation of the new order. O_CARRIER_ID is set to a null value.
      // If the order includes only home order-lines, then O_ALL_LOCAL is set to 1,
      //  otherwise O_ALL_LOCAL is set to 0. (???)
  
      d->d_next_o_id++; Insert_district(trans, d);
      
      o->o_ol_cnt = ol_cnt;
      // From specification: The number of items, O_OL_CNT, is computed to match ol_cnt.
      // Currently we can't tell whether the fscanf() fails, so no way to compute O_OL_CNT,
      //  and we temporarily use a direct assignment. (???)
  
      float sum_ol_amount = 0;
      char brand_generic[15];  // brand-generic (see TPC-C specification section 2.4)
      for (int k = 0; k < ol_cnt; k++)
      {
          int ol_i_id, ol_supply_w_id, ol_quantity;
          fscanf(fp, "%d%d%d", &ol_i_id, &ol_supply_w_id, &ol_quantity);
          orderline_t* ol = new(orderline_t);
          ol->ol_o_id = o->o_id;
          ol->ol_w_id = o->o_w_id; ol->ol_d_id = o->o_d_id;
          ol->ol_i_id = ol_i_id; ol->ol_supply_w_id = ol_supply_w_id;
          ol->ol_quantity = ol_quantity;
  
          item_t* i; Select_item(trans, ol_i_id, &i);
          // The row in the ITEM table with matching I_ID (equals OL_I_ID) is
          //  selected and I_PRICE, the price of the item, I_NAME, the name of
          //  the item, and I_DATA are retrieved.
          // According to spec, a Select in the ITEM table must be done whether or not
          //  the i_id is a null value. So we put this ahead of the following if clause.
  
          if (ol_i_id == 0)
          {
              puts("invalid order-line item id (ol_i_id) in new order transaction");
              tx_trans_abort_n_clear(trans); return;
          }
          // If I_ID has an unused value, a "not-found" condition is signaled,
          //  resulting in a rollback of the database transaction.
  
          stock_t* s; Select_stock(trans, ol_supply_w_id, ol_i_id, &s);
          // The row in the STOCK table with matching S_I_ID (equals OL_I_ID) and S_W_ID (equals
          // OL_SUPPLY_W_ID) is selected. S_QUANTITY, the quantity in stock, S_DIST_xx, where xx
          // represents the district number, and S_DATA are retrieved.
  
          if (s->s_quantity - ol_quantity >= 10) s->s_quantity -= ol_quantity;
          else s->s_quantity = (s->s_quantity - ol_quantity) + 91;
          // If the retrieved value for S_QUANTITY exceeds
          // OL_QUANTITY by 10 or more, then S_QUANTITY is decreased by OL_QUANTITY; otherwise
          // S_QUANTITY is updated to (S_QUANTITY - OL_QUANTITY) + 91.
          
          s->s_ytd += ol_quantity; s->s_order_cnt += 1;
          if (ol_supply_w_id != w_id) { s->s_remote_cnt += 1; o->o_all_local = 0; }
          Insert_stock(trans, s);
          // S_YTD is increased by OL_QUANTITY and S_ORDER_CNT is incremented by 1.
          // If the order-line is remote, then S_REMOTE_CNT is incremented by 1.
  
          ol->ol_amount = ol->ol_quantity * i->i_price;
          sum_ol_amount += ol->ol_amount;
          // The amount for the item in the order (OL_AMOUNT) is computed.
  
          brand_generic[k] = strstr(i->i_data, "original") && strstr(s->s_data, "original") ? 'B' : 'G';
          // The strings in I_DATA and S_DATA are examined. If they both include the
          //  string "ORIGINAL", the brand-generic field for that item is set to "B",
          //  otherwise, the brand-generic field is set to "G".
          // This information is intended for terminal display
  
          ol->ol_delivery_d = NULL; ol->ol_number = k;
          strcpy(ol->ol_dist_info, s->s_dist[ol->ol_d_id]);
          Insert_orderline(trans, ol);
          // A new row is inserted into the ORDER-LINE table to reflect the item on
          //  the order. OL_DELIVERY_D is set to a null value, OL_NUMBER is set to
          //  a unique value within all the ORDER-LINE rows that have the same OL_O_ID
          //  value, and OL_DIST_INFO is set to the content of S_DIST_xx, where xx
          //  represents the district number (OL_D_ID)
      }
      
      float total_amount = sum_ol_amount * (1 - c->c_discount) * (1 + w->w_tax + d->d_tax);
      // The total-amount for the complete order is computed.
      // This information is intended for terminal display
      
      Insert_order(trans, o);
      tx_trans_commit(trans);  tx_trans_destroy(trans);  // ??? destroy a tx every time?
      // The database transaction is committed, unless it has been rolled back
      //  as a result of an unused value for the last item number.
  
      // ...
      // The output data are communicated to the terminal. (Omit)
  }
  void trans_payment(tx_ctx_t* ctx)
  {
      int w_id, d_id, c_w_id, c_d_id, byname;
      float h_amount;
      fscanf(fp, "%d%d%d%d%f%d", &w_id, &d_id, &c_w_id, &c_d_id, &h_amount, &byname);
  
      tx_trans_t* trans = tx_trans_create(ctx);  tx_trans_init(ctx, trans);
  
      warehouse_t* w; Select_warehouse(trans, w_id, &w);
      w->w_ytd += h_amount;
      Insert_warehouse(trans, w);
      
      district_t* d; Select_district(trans, w_id, d_id, &d);
      d->d_ytd += h_amount;
      Insert_district(trans, d);
      
      customer_t* c;
      int c_id;
      char c_last[17];
      if (byname == 2)
      {
          fscanf(fp, "%s", c_last);
          Select_customer_byname(trans, c_w_id, c_d_id, c_last, &c);
          c_id = c->c_id;
      }
      else {
          fscanf(fp, "%d", &c_id);
          Select_customer(trans, c_w_id, c_d_id, c_id, &c);
      }
      c->c_balance -= h_amount;
      c->c_ytd_payment += h_amount;
      c->c_payment_cnt++;
  
      char h_data[101];
      strcpy(h_data, w->w_name); strcat(h_data, "    "); strcat(h_data, d->d_name);
      char c_data[501] = {}, c_new_data[501] = {};
      if (strstr(c->c_credit, "BC"))
      {
          strcpy(c_data, c->c_data);
          sprintf(c_new_data, "| %4d %2d %4d %2d %4d $%7.2f %12s %24s",
          c_id, c_d_id, c_w_id, d_id, w_id, h_amount, asc_local_time(), h_data);  // ???
          strncat(c_new_data, c_data, 500-strlen(c_new_data));  // padding???
          strcpy(c->c_data, c_new_data);
      }
      Insert_customer(trans, c);
  
      history_t* h = new(history_t); h->h_amount = h_amount;
      strcpy(h->h_data, h_data);
      h->h_c_d_id = c_d_id; h->h_c_w_id = c_w_id; h->h_c_id = c_id; h->h_d_id = d_id;
      h->h_w_id = w_id;
      h->h_date = new(struct tm); *h->h_date = cur_local_time();
      Insert_history(trans, h);
      
      tx_trans_commit(trans);  tx_trans_destroy(trans);
  }
  void trans_order_status(tx_ctx_t* ctx)
  {
      int w_id, d_id, byname;
      fscanf(fp, "%d%d%d", &w_id, &d_id, &byname);
  
      tx_trans_t* trans = tx_trans_create(ctx);  tx_trans_init(ctx, trans);
  
      customer_t* c;
      char c_last[17];
      int c_id = 0;
      if (byname == 2)
      {
          fscanf(fp, "%s", c_last);
          Select_customer_byname(trans, w_id, d_id, c_last, &c);
          c_id = c->c_id;
      }
      else {
          fscanf(fp, "%d", &c_id);
          Select_customer(trans, w_id, d_id, c_id, &c);
      }
      
      order_t* o;
      Select_latest_order(trans, w_id, d_id, c_id, &o);
  
      for (int k = 0; k < 15; k++)
      {
          orderline_t* ol; Select_orderline(trans, w_id, d_id, o->o_id, k, &ol);
          if (ol == NULL) break;  // end of orderlines in this order
      }
      
      tx_trans_commit(trans);  tx_trans_destroy(trans);
      // From the spec: A commit is not required as long as all ACID properties are satisfied.
  
      // ...
      // The output data are communicated to the terminal. (Omit)
  }
  int trans_delivery(tx_ctx_t* ctx, time_t created_time, int w_id, int o_carrier_id)  // Deferred Execution
  {
      // returns number of skipped districts (no undelivered orders)
      tx_trans_t* trans = tx_trans_create(ctx);
      fprintf(delivery_tx_result_fp, "Delivery tx created time: %s\n", asctime(localtime(&created_time)));
      fprintf(delivery_tx_result_fp, "W: %d, Order carrier: %d\n", w_id, o_carrier_id);
  
      warehouse_t* w; Select_warehouse(trans, w_id, &w);
      // The deferred execution of the Delivery transaction delivers one outstanding order
      //  (average items-per-order = 10) for each one of the 10 districts of the
      //  selected warehouse using one or more (up to 10) database transactions.
      // NOTE: This business transaction can be done within a single database transaction
      //  or broken down into up to 10 database transactions to allow the test sponsor
      //  the flexibility to implement the business transaction with the most efficient
      //  number of database transactions.
      
      // Delivering each order is done in the following steps:
      int num_skipped = 0;
      for (int d_id = 1; d_id <= 10; d_id++)
      {
          tx_trans_init(ctx, trans);
          
          neworder_t* no; Select_undelivered_neworder(trans, w_id, d_id, &no);
          // This select function can be optimized: we only need no_o_id
          if (no == NULL)
          {
              tx_trans_commit(trans);  // ??? Didn't do anything in this tx, should we commit or abort?
              num_skipped++;
              continue;
          }
          Delete_neworder(trans, no);
  
          order_t* o; Select_order(trans, w_id, d_id, no->no_o_id, &o);
          o -> o_carrier_id = o_carrier_id;
          Insert_order(trans, o);
  
          struct tm cur_time = cur_local_time();
          float o_ol_amount = 0;
          for (int k = 0; k < 15; k++)
          {
              orderline_t* ol; Select_orderline(trans, w_id, d_id, no->no_o_id, k, &ol);
              if (ol == NULL) break;  // end of orderlines in this order
              ol->ol_delivery_d = new(struct tm); *ol->ol_delivery_d = cur_time;  // must be NULL before
              o_ol_amount += ol->ol_amount;
              Insert_orderline(trans, ol);
          }
  
          customer_t* c; Select_customer(trans, w_id, d_id, o->o_c_id, &c);
          c->c_balance += o_ol_amount;
          c->c_delivery_cnt++;
          Insert_customer(trans, c);
  
          tx_trans_commit(trans);
          fprintf(delivery_tx_result_fp, " D: %d, O: %d\n", d_id, o->o_id);
      }
  
      tx_trans_destroy(trans);
      fprintf(delivery_tx_result_fp, "Delivery tx completed time: %s\n", asc_local_time());
      return num_skipped;
  }
  void trans_stock_level(tx_ctx_t* ctx)
  {
      int w_id, d_id, threshold;
      fscanf(fp, "%d%d%d", &w_id, &d_id, &threshold);
  
      tx_trans_t* trans = tx_trans_create(ctx);  tx_trans_init(ctx, trans);
  
      district_t* d; Select_district(trans, w_id, d_id, &d);
      int d_next_o_id = d -> d_next_o_id;
  
      // EXEC SQL SELECT COUNT(DISTINCT (s_i_id)) INTO :stock_count
      // FROM order_line, stock
      // WHERE ol_w_id=:w_id AND
      // ol_d_id=:d_id AND ol_o_id<:o_id AND
      // ol_o_id>=:o_id-20 AND s_w_id=:w_id AND
      // s_i_id=ol_i_id AND s_quantity < :threshold;
      int cnt_low_stock = 0;
      for (int o_id = d_next_o_id - 20; o_id < d_next_o_id; o_id++)
          for (int ol_number = 0; ol_number < 15; ol_number++)
          {
              orderline_t* ol; Select_orderline(trans, w_id, d_id, o_id, ol_number, &ol);
              // TODO: record **distinct** ol_i_id
              stock_t* s; Select_stock(trans, w_id, ol->ol_i_id, &s);
              if (s -> s_quantity < threshold) cnt_low_stock++;
          }
  
      tx_trans_commit(trans);  tx_trans_destroy(trans);
  }
  
  typedef struct trans_delivery_t
  {
      time_t enq_time;
      int w_id;
      int o_carrier_id;
  } trans_delivery_t;  // Used to store delivery tx for deferred execution
  
  void process_trans_from_trace(void)
  {
      tx_ctx_t* ctx = new(tx_ctx_t);
      tx_ctx_init(ctx);
      fp = fopen("trans_trace.txt", "r");
      delivery_tx_result_fp = fopen("delivery_tx_result.txt", "w");
  
      int trans_type;
      static trans_delivery_t que[10001]; int qtop = 0; // queue for deferred execution
      time_t enq_time;  // enqueue time
      int w_id, o_carrier_id;  // for delivery tx
      while (fscanf(fp, "%d", &trans_type) != EOF)
          switch (trans_type)
          {
              case 1: trans_new_order(ctx); break;
              case 2: trans_payment(ctx); break;
              case 3: trans_order_status(ctx); break;
              case 4:  // Deferred Execution 
                  fscanf(fp, "%d%d", &w_id, &o_carrier_id);
                  enq_time = time(NULL);
                  que[qtop++] = (trans_delivery_t){enq_time, w_id, o_carrier_id};
                  break;
              case 5: trans_stock_level(ctx); break;
              default: puts("Error!");
          }
  
      int skipped_districts = 0;
      for (int i = 0; i < qtop; i++)
          skipped_districts += trans_delivery(ctx, que[i].enq_time, que[i].w_id, que[i].o_carrier_id);
      fprintf(delivery_tx_result_fp, "%s\n", asc_local_time());
  
      tx_ctx_destroy(ctx);
      fclose(fp); fclose(delivery_tx_result_fp);
  }
  
  int main(void)
  {
      srand(time(NULL));
      init_db_population(1);
  
      process_trans_from_trace();
  
      return 0;
  }
  