buildUpMigration method

Method buildUpMigration(
  1. OrmBuildContext ctx,
  2. LibraryBuilder lib
)

Generate up() method to create tables

Implementation

Method buildUpMigration(OrmBuildContext ctx, LibraryBuilder lib) {
  return Method((meth) {
    // Check to see if clazz extends Model class
    var autoIdAndDateFields = const TypeChecker.fromRuntime(Model)
        .isAssignableFromType(ctx.buildContext.clazz.thisType);
    meth
      ..name = 'up'
      ..returns = refer('void')
      ..annotations.add(refer('override'))
      ..requiredParameters.add(_schemaParam);

    //var closure = Method.closure()..addPositional(parameter('table'));
    var closure = Method((closure) {
      closure
        ..requiredParameters.add(Parameter((b) => b..name = 'table'))
        ..body = Block((closureBody) {
          var table = refer('table');

          var dup = <String>[];
          ctx.columns.forEach((name, col) {
            // Skip custom-expression columns.
            if (col.hasExpression) return;

            var key = ctx.buildContext.resolveFieldName(name);

            if (dup.contains(key)) {
              return;
            } else {
              // if (key != 'id' || autoIdAndDateFields == false) {
              //   // Check for relationships that might duplicate
              //   for (var rName in ctx.relations.keys) {
              //     var relationship = ctx.relations[rName];
              //     if (relationship.localKey == key) return;
              //   }
              // }

              // Fix from: https://github.com/angel-dart/angel/issues/114#issuecomment-505525729
              if (!(col.indexType == IndexType.primaryKey ||
                  (autoIdAndDateFields != false && name == 'id'))) {
                // Check for relationships that might duplicate
                for (var rName in ctx.relations.keys) {
                  var relationship = ctx.relations[rName]!;
                  if (relationship.localKey == key) return;
                }
              }
              if (key != null) {
                dup.add(key);
              } else {
                print('Skip: key is null');
              }
            }

            String? methodName;
            var positional = <Expression>[literal(key)];
            var named = <String, Expression>{};

            if (autoIdAndDateFields != false && name == 'id') {
              methodName = 'serial';
            }

            if (methodName == null) {
              switch (col.type) {
                case ColumnType.varChar:
                  methodName = 'varChar';
                  if (col.type.hasLength) {
                    named['length'] = literal(col.length);
                  }
                  break;
                case ColumnType.serial:
                  methodName = 'serial';
                  break;
                case ColumnType.int:
                  methodName = 'integer';
                  break;
                case ColumnType.float:
                  methodName = 'float';
                  break;
                case ColumnType.double:
                  methodName = 'double';
                  break;
                case ColumnType.numeric:
                  methodName = 'numeric';
                  if (col.type.hasPrecision) {}
                  break;
                case ColumnType.boolean:
                  methodName = 'boolean';
                  break;
                case ColumnType.date:
                  methodName = 'date';
                  break;
                case ColumnType.dateTime:
                  methodName = 'dateTime';
                  break;
                case ColumnType.timeStamp:
                  methodName = 'timeStamp';
                  break;
                default:
                  Expression provColumn;
                  var colType = refer('Column');
                  var columnTypeType = refer('ColumnType');

                  if (col.length == 0) {
                    methodName = 'declare';
                    provColumn = columnTypeType.newInstance([
                      literal(col.type.name),
                    ]);
                  } else {
                    methodName = 'declareColumn';
                    provColumn = colType.newInstance([], {
                      'type': columnTypeType.newInstance([
                        literal(col.type.name),
                      ]),
                      'length': literal(col.length),
                    });
                  }

                  positional.add(provColumn);
                  break;
              }
            }

            var field = table.property(methodName).call(positional, named);

            var cascade = <Expression>[];

            var defaultValue = ctx.buildContext.defaults[name];

            // Handle 'defaultValue' on Column annotation
            if (col.defaultValue != null) {
              var defaultCode =
                  dartObjectToString(col.defaultValue as DartObject);

              if (defaultCode != null) {
                Expression defaultExpr = CodeExpression(
                  Code(defaultCode),
                );
                cascade.add(refer('defaultsTo').call([defaultExpr]));
              }

              // Handle 'defaultValue' on SerializableField annotation
            } else if (defaultValue != null && !defaultValue.isNull) {
              var type = defaultValue.type;
              Expression? defaultExpr;

              if (const TypeChecker.fromRuntime(RawSql)
                  .isAssignableFromType(defaultValue.type!)) {
                var value =
                    ConstantReader(defaultValue).read('value').stringValue;
                defaultExpr =
                    refer('RawSql').constInstance([literalString(value)]);
              } else if (type is InterfaceType &&
                  type.element is EnumElement) {
                // Default to enum index.
                try {
                  var index =
                      ConstantReader(defaultValue).read('index').intValue;
                  defaultExpr = literalNum(index);
                } catch (_) {
                  // Extremely weird error occurs here: `Not an instance of int`.
                  // Definitely an analyzer issue.
                }
              } else {
                var defaultCode = dartObjectToString(defaultValue);
                if (defaultCode != null) {
                  defaultExpr = CodeExpression(
                    Code(defaultCode),
                  );
                }
              }

              if (defaultExpr != null) {
                cascade.add(refer('defaultsTo').call([defaultExpr]));
              }
            }

            if (col.indexType == IndexType.primaryKey ||
                (autoIdAndDateFields != false && name == 'id')) {
              cascade.add(refer('primaryKey').call([]));
            } else if (col.indexType == IndexType.unique) {
              cascade.add(refer('unique').call([]));
            }

            if (!col.isNullable) {
              cascade.add(refer('notNull').call([]));
            }

            if (cascade.isNotEmpty) {
              var b = StringBuffer()
                ..writeln(
                    field.accept(DartEmitter(useNullSafetySyntax: true)));

              if (cascade.length == 1) {
                var ex = cascade[0];
                b
                  ..write('.')
                  ..writeln(
                      ex.accept(DartEmitter(useNullSafetySyntax: true)));
              } else {
                for (var ex in cascade) {
                  b
                    ..write('..')
                    ..writeln(
                        ex.accept(DartEmitter(useNullSafetySyntax: true)));
                }
              }

              field = CodeExpression(Code(b.toString()));
            }

            closureBody.addExpression(field);
          });

          ctx.relations.forEach((name, r) {
            var relationship = r;

            if (relationship.type == RelationshipType.belongsTo) {
              // Fix from https://github.com/angel-dart/angel/issues/116#issuecomment-505546479
              // var key = relationship.localKey;

              // var field = table.property('integer').call([literal(key)]);
              // // .references('user', 'id').onDeleteCascade()

              // Check to see if foreign clazz extends Model class
              var foreignTableType =
                  relationship.foreign?.buildContext.clazz.thisType;
              var foreignAautoIdAndDateFields = false;
              if (foreignTableType != null) {
                foreignAautoIdAndDateFields =
                    const TypeChecker.fromRuntime(Model)
                        .isAssignableFromType(foreignTableType);
              }

              var columnTypeType = refer('ColumnType');
              var key = relationship.localKey;

              // Default to `int` if foreign class extends Model with implicit 'id'
              // as primary key
              String? keyType;
              if (foreignAautoIdAndDateFields != false &&
                  relationship.foreignKey == "id") {
                keyType = "int";
              } else {
                var foreigColumnName =
                    relationship.foreign?.columns[relationship.foreignKey!];
                keyType = foreigColumnName?.type.name;
              }

              var field = table.property('declare').call([
                literal(key),
                columnTypeType.newInstance([
                  literal(keyType),
                ])
              ]);

              var ref = field.property('references').call([
                literal(relationship.foreignTable),
                literal(relationship.foreignKey),
              ]);

              if (relationship.cascadeOnDelete != false &&
                  const [RelationshipType.hasOne, RelationshipType.belongsTo]
                      .contains(relationship.type)) {
                ref = ref.property('onDeleteCascade').call([]);
              }
              closureBody.addExpression(ref);
            }
          });
        });
    });

    meth.body = Block((b) {
      b.addExpression(_schema.property('create').call([
        literal(ctx.tableName),
        closure.closure,
      ]));
    });
  });
}