예제 #1
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) Insert(m driver.Model, data interface{}) (driver.Result, error) {
	_, fields, values, err := d.saveParameters(m, data)
	if err != nil {
		return nil, err
	}
	buf := getBuffer()
	buf.WriteString("INSERT INTO ")
	buf.WriteByte('"')
	buf.WriteString(m.Table())
	buf.WriteByte('"')
	count := len(fields)
	if count > 0 {
		buf.WriteString(" (")
		for _, v := range fields {
			buf.WriteByte('"')
			buf.WriteString(v)
			buf.WriteByte('"')
			buf.WriteByte(',')
		}
		buf.Truncate(buf.Len() - 1)
		buf.WriteString(") VALUES (")
		buf.WriteString(d.backend.Placeholders(count))
		buf.WriteByte(')')
	} else {
		buf.WriteByte(' ')
		buf.WriteString(d.backend.DefaultValues())
	}
	res, err := d.backend.Insert(d.db, m, buftos(buf), values...)
	putBuffer(buf)
	return res, err
}
예제 #2
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) Update(m driver.Model, q query.Q, data interface{}) (driver.Result, error) {
	_, fields, values, err := d.saveParameters(m, data)
	if err != nil {
		return nil, err
	}
	buf := getBuffer()
	buf.WriteString("UPDATE ")
	buf.WriteByte('"')
	buf.WriteString(m.Table())
	buf.WriteByte('"')
	buf.WriteString(" SET ")
	for ii, v := range fields {
		buf.WriteByte('"')
		buf.WriteString(v)
		buf.WriteByte('"')
		buf.WriteByte('=')
		buf.WriteString(d.backend.Placeholder(ii))
		buf.WriteByte(',')
	}
	// remove last ,
	buf.Truncate(buf.Len() - 1)
	qParams, err := d.where(buf, m, q, len(values))
	if err != nil {
		return nil, err
	}
	params := append(values, qParams...)
	res, err := d.db.Exec(buftos(buf), params...)
	putBuffer(buf)
	return res, err
}
예제 #3
0
파일: table.go 프로젝트: rainycape/gondola
func (t *Table) SQL(db *DB, b Backend, m driver.Model, name string) (string, error) {
	var lines []string
	var constraints []string
	for _, v := range t.Fields {
		def, cons, err := v.SQL(db, m, t)
		if err != nil {
			return "", err
		}
		lines = append(lines, def)
		constraints = append(constraints, cons...)
	}
	pk, err := t.definePks(db, m)
	if err != nil {
		return "", err
	}
	if pk != "" {
		lines = append(lines, pk)
	}
	lines = append(lines, constraints...)
	if name == "" {
		name = m.Table()
	}
	// Use IF NOT EXISTS, since the DB user might not have
	// the privileges to inspect the database but still
	// be allowed to read and write from the tables (e.g.
	// Postgres only allows superusers and the owner to
	// inspect the database).
	sql := fmt.Sprintf("\nCREATE TABLE IF NOT EXISTS %s (\n\t%s\n)", db.QuoteIdentifier(name), strings.Join(lines, ",\n\t"))
	return sql, nil
}
예제 #4
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) clause(buf *bytes.Buffer, params *[]interface{}, m driver.Model, format string, f *query.Field, begin int) error {
	dbName, _, err := m.Map(f.Field)
	if err != nil {
		return err
	}
	if f.Value != nil {
		if field, ok := f.Value.(query.F); ok {
			fName, _, err := m.Map(string(field))
			if err != nil {
				return err
			}
			fmt.Fprintf(buf, format, dbName, fName)
			return nil
		}
		if sq, ok := f.Value.(query.Subquery); ok {
			fmt.Fprintf(buf, format, dbName, "("+string(sq)+")")
			return nil
		}
		fmt.Fprintf(buf, format, dbName, d.backend.Placeholder(len(*params)+begin))
		val, err := d.transformOutValue(f.Value)
		if err != nil {
			return err
		}
		*params = append(*params, val)
		return nil
	}
	fmt.Fprintf(buf, format, dbName)
	return nil
}
예제 #5
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) makeQuery(m driver.Model, q query.Q, opts *driver.QueryOptions) (*datastore.Query, error) {
	if m.Join() != nil {
		return nil, errJoinNotSupported
	}
	dq := datastore.NewQuery(m.Table()).Ancestor(d.parentKey(m))
	var err error
	if dq, err = d.applyQuery(m, dq, q); err != nil {
		return nil, err
	}
	if opts != nil {
		if opts.Distinct {
			dq = dq.Distinct()
		}
		for _, v := range opts.Sort {
			field := v.Field()
			if v.Direction() == driver.DESC {
				field = "-" + field
			}
			dq = dq.Order(field)
		}
		if opts.Limit >= 0 {
			dq = dq.Limit(opts.Limit)
		}
		if opts.Offset > 0 {
			dq = dq.Offset(opts.Offset)
		}
	}
	return dq, nil
}
예제 #6
0
파일: driver.go 프로젝트: rainycape/gondola
func fieldHasDefault(m driver.Model, f *Field) bool {
	if f.Default != "" {
		return true
	}
	fields := m.Fields()
	idx := fields.MNameMap[f.Name]
	return fields.HasDefault(idx)
}
예제 #7
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) createTable(m driver.Model, table *Table) error {
	sql, err := table.SQL(d.db, d.backend, m, m.Table())
	if err != nil {
		return err
	}
	_, err = d.db.Exec(sql)
	return err
}
예제 #8
0
func (b *Backend) HasIndex(db *sql.DB, m driver.Model, idx *index.Index, name string) (bool, error) {
	rows, err := db.Query("SHOW INDEX FROM ? WHERE Key_name = ?", m.Table(), name)
	if err != nil {
		return false, err
	}
	has := rows.Next()
	rows.Close()
	return has, nil
}
예제 #9
0
func (b *Backend) Inspect(db *sql.DB, m driver.Model) (*sql.Table, error) {
	name := db.QuoteString(m.Table())
	rows, err := db.Query(fmt.Sprintf("PRAGMA table_info(%s)", name))
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	fieldsByName := make(map[string]*sql.Field)
	var fields []*sql.Field
	for rows.Next() {
		var cid int
		var f sql.Field
		var notnull int
		var def *string
		var pk int
		if err := rows.Scan(&cid, &f.Name, &f.Type, &notnull, &def, &pk); err != nil {
			return nil, err
		}
		f.Type = strings.ToUpper(f.Type)
		if notnull != 0 {
			f.AddConstraint(sql.ConstraintNotNull)
		}
		if def != nil {
			f.Default = *def
		}
		if pk != 0 {
			f.AddConstraint(sql.ConstraintPrimaryKey)
		}
		fields = append(fields, &f)
		fieldsByName[f.Name] = &f
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	rows, err = db.Query(fmt.Sprintf("PRAGMA foreign_key_list(%s)", name))
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var id, seq int
		var table, from, to, onUpdate, onDelete, match string
		if err := rows.Scan(&id, &seq, &table, &from, &to, &onUpdate, &onDelete, &match); err != nil {
			return nil, err
		}
		field := fieldsByName[from]
		field.Constraints = append(field.Constraints, &sql.Constraint{Type: sql.ConstraintForeignKey, References: sql.MakeReference(table, to)})
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	if len(fields) > 0 {
		return &sql.Table{Fields: fields}, nil
	}
	return nil, nil
}
예제 #10
0
파일: model.go 프로젝트: rainycape/gondola
func (s sortModels) less(mi, mj driver.Model) bool {
	for _, v := range mi.Fields().References {
		if v.Model == mj {
			return false
		}
		if v.Model != mi && !s.less(v.Model, mj) {
			return false
		}
	}
	return true
}
예제 #11
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) createIndexes(m driver.Model) error {
	for _, idx := range m.Indexes() {
		name, err := d.indexName(m, idx)
		if err != nil {
			return err
		}
		if err := d.createIndex(m, idx, name); err != nil {
			return err
		}
	}
	return nil
}
예제 #12
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) Delete(m driver.Model, q query.Q) (driver.Result, error) {
	buf := getBuffer()
	buf.WriteString("DELETE FROM ")
	buf.WriteByte('"')
	buf.WriteString(m.Table())
	buf.WriteByte('"')
	params, err := d.where(buf, m, q, 0)
	if err != nil {
		return nil, err
	}
	res, err := d.db.Exec(buftos(buf), params...)
	putBuffer(buf)
	return res, err
}
예제 #13
0
func (b *Backend) Insert(db *sql.DB, m driver.Model, query string, args ...interface{}) (driver.Result, error) {
	fields := m.Fields()
	if fields.AutoincrementPk {
		q := query + " RETURNING " + fields.MNames[fields.PrimaryKey]
		var id int64
		err := db.QueryRow(q, args...).Scan(&id)
		// We need to perform a "real" insert to find the real error, so
		// just let the code fall to the Exec at the end of the function
		// if there's an error.
		if err == nil {
			return insertResult(id), nil
		}
	}
	return db.Exec(query, args...)
}
예제 #14
0
func (b *SqlBackend) AddFields(db *DB, m driver.Model, prevTable *Table, newTable *Table, fields []*Field) error {
	modelFields := m.Fields()
	tableName := db.QuoteIdentifier(m.Table())
	for _, v := range fields {
		idx := modelFields.MNameMap[v.Name]
		field := v
		hasDefault := modelFields.HasDefault(idx)
		if hasDefault && v.HasConstraint(ConstraintNotNull) {
			// ORM level default
			// Must be added as nullable first, then the default value
			// must be set and finally the field has to be altered to be
			// nullable.
			field = field.Copy()
			var constraints []*Constraint
			for _, v := range field.Constraints {
				if v.Type != ConstraintNotNull {
					constraints = append(constraints, v)
				}
			}
			field.Constraints = constraints
		}
		sql, cons, err := field.SQL(db, m, newTable)
		if err != nil {
			return err
		}
		if _, err = db.Exec(fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s", tableName, sql)); err != nil {
			return err
		}
		if hasDefault {
			value := modelFields.DefaultValue(idx)
			fieldName := db.QuoteIdentifier(v.Name)
			if _, err := db.Exec(fmt.Sprintf("UPDATE %s SET %s = ?", tableName, fieldName), value); err != nil {
				return err
			}
			if v.HasConstraint(ConstraintNotNull) {
				if err := db.Backend().AlterField(db, m, newTable, field, v); err != nil {
					return err
				}
			}
		}
		for _, c := range cons {
			if _, err = db.Exec(fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s", tableName, c)); err != nil {
				return err
			}
		}
	}
	return nil
}
예제 #15
0
func (b *Backend) AlterField(db *sql.DB, m driver.Model, table *sql.Table, oldField *sql.Field, newField *sql.Field) error {
	fsql, cons, err := newField.SQL(db, m, table)
	if err != nil {
		return err
	}
	tableName := db.QuoteIdentifier(m.Table())
	if _, err = db.Exec(fmt.Sprintf("ALTER TABLE %s CHANGE COLUMN %s %s", tableName, db.QuoteIdentifier(oldField.Name), fsql)); err != nil {
		return err
	}
	for _, c := range cons {
		if _, err = db.Exec(fmt.Sprintf("ALTER TABLE %s ADD CONSTRAINT %s", tableName, c)); err != nil {
			return err
		}
	}
	return err
}
예제 #16
0
func (b *Backend) DefineField(db *sql.DB, m driver.Model, table *sql.Table, field *sql.Field) (string, []string, error) {
	def, cons, err := b.SqlBackend.DefineField(db, m, table, field)
	if err != nil {
		return "", nil, err
	}
	if ref := field.Constraint(sql.ConstraintForeignKey); ref != nil {
		if pos := strings.Index(def, " REFERENCES"); pos >= 0 {
			def = def[:pos]
		}
		refTable := ref.References.Table()
		refField := ref.References.Field()
		fkName := db.QuoteIdentifier(fmt.Sprintf("%s_%s_%s_%s", m.Table(), field.Name, refTable, refField))
		cons = append(cons, fmt.Sprintf("FOREIGN KEY %s(%s) REFERENCES %s(%s)", fkName, db.QuoteIdentifier(field.Name),
			db.QuoteIdentifier(refTable), db.QuoteIdentifier(refField)))
	}
	return strings.Replace(def, "AUTOINCREMENT", "AUTO_INCREMENT", -1), cons, nil
}
예제 #17
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) outValues(m driver.Model, out interface{}) (reflect.Value, *driver.Fields, []interface{}, []*scanner, error) {
	val := reflect.ValueOf(out)
	if !val.IsValid() {
		// Untyped nil pointer
		return reflect.Value{}, nil, nil, nil, nil
	}
	vt := val.Type()
	if vt.Kind() != reflect.Ptr {
		return reflect.Value{}, nil, nil, nil, fmt.Errorf("can't set object of type %T. Please, pass a %v rather than a %v", out, reflect.PtrTo(vt), vt)
	}
	if vt.Elem().Kind() == reflect.Ptr && vt.Elem().Elem().Kind() == reflect.Struct {
		// Received a pointer to pointer. Always create a new object,
		// to avoid overwriting the previous result.
		val = val.Elem()
		el := reflect.New(val.Type().Elem())
		val.Set(el)
	}
	for val.Kind() == reflect.Ptr {
		el := val.Elem()
		if !el.IsValid() {
			if !val.CanSet() {
				// Typed nil pointer
				return reflect.Value{}, nil, nil, nil, nil
			}
			el = reflect.New(val.Type().Elem())
			val.Set(el)
		}
		val = el
	}
	fields := m.Fields()
	if fields == nil {
		// Skipped model
		return reflect.Value{}, nil, nil, nil, nil
	}
	values := make([]interface{}, len(fields.Indexes))
	scanners := make([]*scanner, len(fields.Indexes))
	for ii, v := range fields.Indexes {
		field := d.fieldByIndex(val, v, true)
		tag := fields.Tags[ii]
		s := newScanner(&field, tag, d.backend)
		scanners[ii] = s
		values[ii] = s
	}
	return val, fields, values, scanners, nil
}
예제 #18
0
func (b *Backend) AddFields(db *sql.DB, m driver.Model, prevTable *sql.Table, newTable *sql.Table, fields []*sql.Field) error {
	rewrite := false
	for _, v := range fields {
		if !b.canAddField(v) {
			rewrite = true
			break
		}
	}
	if rewrite {
		name := db.QuoteIdentifier(m.Table())
		tmpName := fmt.Sprintf("%s_%s", m.Table(), stringutil.Random(8))
		quotedTmpName := db.QuoteIdentifier(tmpName)
		createSql, err := newTable.SQL(db, b, m, tmpName)
		if err != nil {
			return err
		}
		if _, err := db.Exec(createSql); err != nil {
			return err
		}
		fieldNames := generic.Map(prevTable.Fields, func(f *sql.Field) string { return f.Name }).([]string)
		// The previous table might have fields that we're not part
		// of the new table.
		fieldSet := make(map[string]bool)
		for _, v := range newTable.Fields {
			fieldSet[v.Name] = true
		}
		fieldNames = generic.Filter(fieldNames, func(n string) bool { return fieldSet[n] }).([]string)
		sqlFields := strings.Join(generic.Map(fieldNames, db.QuoteIdentifier).([]string), ", ")
		copySql := fmt.Sprintf("INSERT INTO %s (%s) SELECT %s FROM %s", quotedTmpName, sqlFields, sqlFields, name)
		if _, err := db.Exec(copySql); err != nil {
			return err
		}
		if _, err := db.Exec(fmt.Sprintf("DROP TABLE %s", name)); err != nil {
			return err
		}
		if _, err := db.Exec(fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quotedTmpName, name)); err != nil {
			return err
		}
		return nil
	}
	return b.SqlBackend.AddFields(db, m, prevTable, newTable, fields)
}
예제 #19
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) Operate(m driver.Model, q query.Q, ops []*operation.Operation) (driver.Result, error) {
	buf := getBuffer()
	buf.WriteString("UPDATE ")
	buf.WriteByte('"')
	buf.WriteString(m.Table())
	buf.WriteByte('"')
	buf.WriteString(" SET ")
	var params []interface{}
	for ii, op := range ops {
		if ii > 0 {
			buf.WriteByte(',')
		}
		dbName, _, err := m.Map(op.Field)
		if err != nil {
			return nil, err
		}
		dbName = unquote(dbName)
		buf.WriteByte('"')
		buf.WriteString(dbName)
		buf.WriteByte('"')
		buf.WriteByte('=')
		switch op.Operator {
		case operation.OpAdd, operation.OpSub:
			buf.WriteString(dbName)
			if op.Operator == operation.OpAdd {
				buf.WriteByte('+')
			} else {
				buf.WriteByte('-')
			}
			buf.WriteString(d.backend.Placeholder(len(params)))
			params = append(params, op.Value)
		case operation.OpSet:
			if f, ok := op.Value.(operation.Field); ok {
				fieldName, _, err := m.Map(string(f))
				if err != nil {
					return nil, err
				}
				buf.WriteString(unquote(fieldName))
			} else {
				buf.WriteString(d.backend.Placeholder(len(params)))
				val, err := d.transformOutValue(op.Value)
				if err != nil {
					return nil, err
				}
				params = append(params, val)
			}
		default:
			return nil, fmt.Errorf("operator %d is not supported", op.Operator)
		}
	}
	qParams, err := d.where(buf, m, q, len(params))
	if err != nil {
		return nil, err
	}
	params = append(params, qParams...)
	res, err := d.db.Exec(buftos(buf), params...)
	putBuffer(buf)
	return res, err
}
예제 #20
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) createIndex(m driver.Model, idx *index.Index, name string) error {
	has, err := d.backend.HasIndex(d.db, m, idx, name)
	if err != nil {
		return err
	}
	if has {
		return nil
	}

	buf := getBuffer()
	buf.WriteString("CREATE ")
	if idx.Unique {
		buf.WriteString("UNIQUE ")
	}
	buf.WriteString("INDEX ")
	buf.WriteString(name)
	buf.WriteString(" ON \"")
	buf.WriteString(m.Table())
	buf.WriteString("\" (")
	fields := m.Fields()
	for _, v := range idx.Fields {
		name, _, err := fields.Map(v)
		if err != nil {
			return err
		}
		buf.WriteByte('"')
		buf.WriteString(name)
		buf.WriteByte('"')
		if DescField(idx, v) {
			buf.WriteString(" DESC")
		}
		buf.WriteByte(',')
	}
	buf.Truncate(buf.Len() - 1)
	buf.WriteString(")")
	_, err = d.db.Exec(buftos(buf))
	putBuffer(buf)
	return err
}
예제 #21
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) Insert(m driver.Model, data interface{}) (driver.Result, error) {
	var id int64
	fields := m.Fields()
	var pkVal *reflect.Value
	// TODO: If the PK is supplied by the user rather than auto-assigned, it
	// might conflict with PKs generated by datastore.AllocateIDs().
	if fields.PrimaryKey >= 0 {
		p := d.primaryKey(fields, data)
		if p.IsValid() && types.Kind(p.Kind()) == types.Int {
			id = p.Int()
			if id == 0 {
				// Must assign PK field value after calling AllocateIDs
				pkVal = &p
			}
		}
	}
	name := m.Table()
	// Make all objects of a given kind ancestors of the same key. While
	// this hurts scalability, it makes all reads strongly consistent.
	parent := d.parentKey(m)
	var err error
	if id == 0 {
		id, _, err = datastore.AllocateIDs(d.c, name, parent, 1)
		if err != nil {
			return nil, err
		}
	}
	if fields.AutoincrementPk && pkVal != nil {
		pkVal.SetInt(int64(id))
	}
	key := datastore.NewKey(d.c, name, "", id, parent)
	log.Debugf("DATASTORE: put %s %v", key, data)
	_, err = datastore.Put(d.c, key, data)
	if err != nil {
		return nil, err
	}
	return &result{key: key, count: 1}, nil
}
예제 #22
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) Select(fields []string, quote bool, m driver.Model, q query.Q, opts driver.QueryOptions) (*bytes.Buffer, []interface{}, error) {
	buf := getBuffer()
	var params []interface{}
	if err := d.selectStmt(buf, &params, fields, quote, m, opts); err != nil {
		return nil, nil, err
	}
	qParams, err := d.where(buf, m, q, 0)
	if err != nil {
		return nil, nil, err
	}
	params = append(params, qParams...)
	if len(opts.Sort) > 0 {
		buf.WriteString(" ORDER BY ")
		for _, v := range opts.Sort {
			dbName, _, err := m.Map(v.Field())
			if err != nil {
				return nil, nil, err
			}
			buf.WriteString(dbName)
			if v.Direction() == driver.DESC {
				buf.WriteString(" DESC")
			}
			buf.WriteByte(',')
		}
		buf.Truncate(buf.Len() - 1)
	}
	if opts.Limit >= 0 {
		buf.WriteString(" LIMIT ")
		buf.WriteString(strconv.Itoa(opts.Limit))
	}
	if opts.Offset >= 0 {
		buf.WriteString(" OFFSET ")
		buf.WriteString(strconv.Itoa(opts.Offset))
	}
	return buf, params, nil
}
예제 #23
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) mergeTable(m driver.Model, prevTable *Table, newTable *Table) error {
	existing := make(map[string]*Field)
	for _, v := range prevTable.Fields {
		existing[v.Name] = v
	}
	var missing []*Field
	for _, v := range newTable.Fields {
		prev := existing[v.Name]
		if prev == nil {
			// Check if we can add the field
			if v.HasConstraint(ConstraintNotNull) && !fieldHasDefault(m, v) {
				return fmt.Errorf("can't add NOT NULL field %q to table %q without a default value", v.Name, m.Table())
			}
			if v.HasConstraint(ConstraintPrimaryKey) {
				return fmt.Errorf("can't add PRIMARY KEY field %q to table %q", v.Name, m.Table())
			}
			missing = append(missing, v)
		} else {
			if prev.Type != v.Type {
				// Check the Kind
				k1, len1 := TypeKind(prev.Type)
				k2, len2 := TypeKind(v.Type)
				if k1 == k2 {
					// Check lengths
					if len1 != len2 {
					}
					continue
				}
				// Check if we can transform the kind
				fields := m.Fields()
				idx := fields.MNameMap[v.Name]
				modelName := fields.QNames[idx]
				modelType := fields.Types[idx]
				return fmt.Errorf("field %q on table %q is of type %s which is not compatible with the model field %q of type %s (%s)",
					v.Name, m.Table(), prev.Type, modelName, v.Type, modelType)
			}
		}
	}
	if len(missing) > 0 {
		if err := d.backend.AddFields(d.db, m, prevTable, newTable, missing); err != nil {
			return err
		}
	}
	return nil
}
예제 #24
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) indexName(m driver.Model, idx *index.Index) (string, error) {
	if len(idx.Fields) == 0 {
		return "", fmt.Errorf("index on %v has no fields", m.Type())
	}
	buf := getBuffer()
	buf.WriteString(m.Table())
	for _, v := range idx.Fields {
		dbName, _, err := m.Map(v)
		if err != nil {
			return "", err
		}
		buf.WriteByte('_')
		// dbName is quoted and includes the table name
		// extract the unquoted field name.
		buf.WriteString(unquote(dbName))
		if DescField(idx, v) {
			buf.WriteString("_desc")
		}
	}
	s := buf.String()
	putBuffer(buf)
	return s, nil
}
예제 #25
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) saveParameters(m driver.Model, data interface{}) (reflect.Value, []string, []interface{}, error) {
	// data is guaranteed to be of m.Type()
	val := driver.Direct(reflect.ValueOf(data))
	fields := m.Fields()
	max := len(fields.MNames)
	names := make([]string, 0, max)
	values := make([]interface{}, 0, max)
	var err error
	if d.transforms != nil {
		for ii, v := range fields.Indexes {
			f := d.fieldByIndex(val, v, false)
			if !f.IsValid() {
				continue
			}
			if fields.OmitEmpty[ii] && driver.IsZero(f) {
				continue
			}
			ft := f.Type()
			var fval interface{}
			if _, ok := d.transforms[ft]; ok {
				fval, err = d.backend.TransformOutValue(f)
				if err != nil {
					return val, nil, nil, err
				}
				if fields.NullEmpty[ii] && driver.IsZero(reflect.ValueOf(fval)) {
					fval = nil
				}
			} else if !fields.NullEmpty[ii] || !driver.IsZero(f) {
				if c := codec.FromTag(fields.Tags[ii]); c != nil {
					fval, err = c.Encode(f.Interface())
					if err != nil {
						return val, nil, nil, err
					}
					if p := pipe.FromTag(fields.Tags[ii]); p != nil {
						data, err := p.Encode(fval.([]byte))
						if err != nil {
							return val, nil, nil, err
						}
						fval = data
					}
				} else {
					// Most sql drivers won't accept aliases for string type
					if ft.Kind() == reflect.String && ft != stringType {
						f = f.Convert(stringType)
					}
					fval = f.Interface()
				}
			}
			names = append(names, fields.MNames[ii])
			values = append(values, fval)
		}
	} else {
		for ii, v := range fields.Indexes {
			f := d.fieldByIndex(val, v, false)
			if !f.IsValid() {
				continue
			}
			if fields.OmitEmpty[ii] && driver.IsZero(f) {
				continue
			}
			var fval interface{}
			if !fields.NullEmpty[ii] || !driver.IsZero(f) {
				if c := codec.FromTag(fields.Tags[ii]); c != nil {
					fval, err = c.Encode(&f)
					if err != nil {
						return val, nil, nil, err
					}
				} else {
					ft := f.Type()
					// Most sql drivers won't accept aliases for string type
					if ft.Kind() == reflect.String && ft != stringType {
						f = f.Convert(stringType)
					}
					fval = f.Interface()
				}
			}
			names = append(names, fields.MNames[ii])
			values = append(values, fval)
		}
	}
	return val, names, values, nil
}
예제 #26
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) selectStmt(buf *bytes.Buffer, params *[]interface{}, fields []string, quote bool, m driver.Model, opts driver.QueryOptions) error {
	buf.WriteString("SELECT ")
	if opts.Distinct {
		buf.WriteString("DISTINCT ")
	}
	if fields != nil {
		if quote {
			for _, v := range fields {
				buf.WriteByte('"')
				buf.WriteString(v)
				buf.WriteByte('"')
				buf.WriteByte(',')
			}
		} else {
			for _, v := range fields {
				buf.WriteString(v)
				buf.WriteByte(',')
			}
		}
	} else {
		// Select all fields for the given model (which might be joined)
		cur := m
		for {
			if !cur.Skip() {
				for _, v := range cur.Fields().QuotedNames {
					buf.WriteString(v)
					buf.WriteByte(',')
				}
			}
			join := cur.Join()
			if join == nil {
				break
			}
			cur = join.Model()
		}
	}
	buf.Truncate(buf.Len() - 1)
	buf.WriteString(" FROM ")
	buf.WriteByte('"')
	buf.WriteString(m.Table())
	buf.WriteByte('"')
	for join := m.Join(); join != nil; {
		jm := join.Model()
		switch join.Type() {
		case driver.OuterJoin:
			buf.WriteString(" FULL OUTER")
		case driver.LeftJoin:
			buf.WriteString(" LEFT OUTER")
		case driver.RightJoin:
			buf.WriteString(" RIGHT OUTER")
		}
		buf.WriteString(" JOIN ")
		buf.WriteByte('"')
		buf.WriteString(jm.Table())
		buf.WriteByte('"')
		buf.WriteString(" ON ")
		if err := d.condition(buf, params, m, join.Query(), len(*params)); err != nil {
			return err
		}
		join = jm.Join()
	}
	return nil
}
예제 #27
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) condition(buf *bytes.Buffer, params *[]interface{}, m driver.Model, q query.Q, begin int) error {
	var err error
	switch x := q.(type) {
	case *query.Eq:
		if isNil(x.Value) {
			x.Value = nil
			err = d.clause(buf, params, m, "%s IS NULL", &x.Field, begin)
		} else {
			err = d.clause(buf, params, m, "%s = %s", &x.Field, begin)
		}
	case *query.Neq:
		if isNil(x.Value) {
			x.Value = nil
			err = d.clause(buf, params, m, "%s IS NOT NULL", &x.Field, begin)
		} else {
			err = d.clause(buf, params, m, "%s != %s", &x.Field, begin)
		}
	case *query.Contains:
		err = d.clause(buf, params, m, "%s LIKE '%%' || %s || '%%'", &x.Field, begin)
	case *query.Lt:
		err = d.clause(buf, params, m, "%s < %s", &x.Field, begin)
	case *query.Lte:
		err = d.clause(buf, params, m, "%s <= %s", &x.Field, begin)
	case *query.Gt:
		err = d.clause(buf, params, m, "%s > %s", &x.Field, begin)
	case *query.Gte:
		err = d.clause(buf, params, m, "%s >= %s", &x.Field, begin)
	case *query.Operator:
		err = d.clause(buf, params, m, "%s "+x.Operator+" %s", &x.Field, begin)
	case *query.In:
		dbName, _, err := m.Map(x.Field.Field)
		if err != nil {
			return err
		}
		buf.WriteString(dbName)
		buf.WriteString(" IN (")
		value := reflect.ValueOf(x.Value)
		switch {
		case value.Type() == subqueryType:
			buf.WriteString(value.String())
		case value.Type().Kind() == reflect.Slice || value.Type().Kind() == reflect.Array:
			vLen := value.Len()
			if vLen == 0 {
				return fmt.Errorf("empty IN (field %s)", x.Field.Field)
			}
			jj := len(*params) + begin
			for ii := 0; ii < vLen; ii++ {
				*params = append(*params, value.Index(ii).Interface())
				buf.WriteString(d.backend.Placeholder(jj))
				buf.WriteByte(',')
				jj++
			}
			buf.Truncate(buf.Len() - 1)
		default:
			return fmt.Errorf("argument for IN must be slice or array or query.Subquery (field %s)", x.Field.Field)
		}
		buf.WriteByte(')')
	case *query.And:
		err = d.conditions(buf, params, m, x.Conditions, " AND ", begin)
	case *query.Or:
		err = d.conditions(buf, params, m, x.Conditions, " OR ", begin)
	default:
		err = fmt.Errorf("unhandled operand %T (%v)", x, x)
	}
	return err
}
예제 #28
0
func (b *SqlBackend) Inspect(db *DB, m driver.Model, schema string) (*Table, error) {
	var val int
	name := db.QuoteString(m.Table())
	s := db.QuoteString(schema)
	eq := fmt.Sprintf("SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE "+
		"TABLE_NAME = %s AND TABLE_SCHEMA = %s", name, s)
	err := db.QueryRow(eq).Scan(&val)
	if err != nil {
		if err == ErrNoRows {
			return nil, nil
		}
		return nil, err
	}
	// Select fields with their types
	iq := fmt.Sprintf("SELECT COLUMN_NAME, IS_NULLABLE, DATA_TYPE, "+
		"CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS "+
		"WHERE TABLE_NAME = %s AND TABLE_SCHEMA = %s", name, s)
	rows, err := db.Query(iq)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var fields []*Field
	fieldsByName := make(map[string]*Field)
	for rows.Next() {
		var f Field
		var nullable string
		var maxLength *int
		if err := rows.Scan(&f.Name, &nullable, &f.Type, &maxLength); err != nil {
			return nil, err
		}
		if maxLength != nil {
			f.Type = fmt.Sprintf("%s (%d)", f.Type, *maxLength)
		}
		f.Type = strings.ToUpper(f.Type)
		if nullable != "YES" {
			f.AddConstraint(ConstraintNotNull)
		}
		fields = append(fields, &f)
		fieldsByName[f.Name] = &f
	}
	// Field constraints
	cq := fmt.Sprintf("SELECT C.CONSTRAINT_NAME, CONSTRAINT_TYPE, COLUMN_NAME "+
		"FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS C JOIN "+
		"INFORMATION_SCHEMA.KEY_COLUMN_USAGE K ON C.CONSTRAINT_NAME = "+
		"K.CONSTRAINT_NAME WHERE C.TABLE_NAME = %s AND K.TABLE_NAME = %s "+
		"AND C.TABLE_SCHEMA = %s", name, name, s)
	rows, err = db.Query(cq)
	if err != nil {
		return nil, err
	}
	foreignKeys := make(map[string]string)
	defer rows.Close()
	for rows.Next() {
		var constraintName string
		var constraintType string
		var name string
		if err := rows.Scan(&constraintName, &constraintType, &name); err != nil {
			return nil, err
		}
		field := fieldsByName[name]
		if field == nil {
			return nil, fmt.Errorf("table %s has constraint on non-existing field %s", m.Table(), name)
		}
		switch strings.ToLower(constraintType) {
		case "primary key":
			field.AddConstraint(ConstraintPrimaryKey)
		case "foreign key":
			foreignKeys[constraintName] = name
		case "unique":
			field.AddConstraint(ConstraintUnique)
		default:
			return nil, fmt.Errorf("unknown constraint type %s on field %s in table %s", constraintType, name, m.Table())
		}
	}
	if len(foreignKeys) > 0 {
		// Resolve FKs
		fks := strings.Join(generic.Map(generic.Keys(foreignKeys).([]string), db.QuoteString).([]string), ", ")
		fq := fmt.Sprintf("SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE CONSTRAINT_NAME IN (%s)", fks)
		rows, err := db.Query(fq)
		if err != nil {
			return nil, err
		}
		defer rows.Close()
		for rows.Next() {
			var constraintName string
			var tableName string
			var columnName string
			if err := rows.Scan(&constraintName, &tableName, &columnName); err != nil {
				return nil, err
			}
			fieldName := foreignKeys[constraintName]
			// Field was validated previously, won't be nil
			field := fieldsByName[fieldName]
			field.Constraints = append(field.Constraints, &Constraint{Type: ConstraintForeignKey, References: MakeReference(tableName, columnName)})
		}
	}
	return &Table{Fields: fields}, nil
}
예제 #29
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) makeTable(m driver.Model) (*Table, error) {
	fields := m.Fields()
	names := fields.MNames
	qnames := fields.QNames
	ftypes := fields.Types
	tags := fields.Tags
	dbFields := make([]*Field, len(names))
	for ii, v := range names {
		typ := ftypes[ii]
		tag := tags[ii]
		ft, err := d.backend.FieldType(typ, tag)
		if err != nil {
			return nil, err
		}
		def := tag.Value("default")
		if fields.HasDefault(ii) {
			// Handled by the ORM
			def = ""
		}
		if def != "" {
			if driver.IsFunc(def) {
				fname, _ := driver.SplitFuncArgs(def)
				fn, err := d.backend.Func(fname, ftypes[ii])
				if err != nil {
					if err == ErrFuncNotSupported {
						err = fmt.Errorf("backend %s does not support function %s", d.backend.Name(), tag.Value("default"))
					}
					return nil, err
				}
				def = fn
			} else {
				def = driver.UnescapeDefault(def)
				if typ.Kind() == reflect.String {
					def = d.db.QuoteString(def)
				}
			}
		}
		field := &Field{
			Name:    v,
			Type:    ft,
			Default: def,
		}
		if tag.Has("notnull") {
			field.AddConstraint(ConstraintNotNull)
		}
		if d.isPrimaryKey(fields, ii, tag) {
			field.AddConstraint(ConstraintPrimaryKey)
		} else if tag.Has("unique") {
			field.AddConstraint(ConstraintUnique)
		}
		if tag.Has("auto_increment") {
			field.AddOption(OptionAutoIncrement)
		}
		if ref := fields.References[qnames[ii]]; ref != nil {
			fk, _, err := ref.Model.Fields().Map(ref.Field)
			if err != nil {
				return nil, err
			}
			field.Constraints = append(field.Constraints, &Constraint{
				Type:       ConstraintForeignKey,
				References: MakeReference(ref.Model.Table(), fk),
			})
		}
		dbFields[ii] = field
	}
	return &Table{Fields: dbFields}, nil
}
예제 #30
0
파일: driver.go 프로젝트: rainycape/gondola
func (d *Driver) applyQuery(m driver.Model, dq *datastore.Query, q query.Q) (*datastore.Query, error) {
	var field *query.Field
	var op string
	switch x := q.(type) {
	case *query.Eq:
		field = &x.Field
		op = " ="
	case *query.Lt:
		field = &x.Field
		op = " <"
	case *query.Lte:
		field = &x.Field
		op = " <="
	case *query.Gt:
		field = &x.Field
		op = " >"
	case *query.Gte:
		field = &x.Field
		op = " >="
	case *query.And:
		var err error
		for _, v := range x.Conditions {
			dq, err = d.applyQuery(m, dq, v)
			if err != nil {
				return nil, err
			}
		}
	case nil:
	default:
		return nil, fmt.Errorf("datastore does not support %T queries", q)
	}
	if field != nil {
		if _, ok := field.Value.(query.F); ok {
			return nil, fmt.Errorf("datastore queries can't reference other properties (%v)", field.Value)
		}
		name := field.Field
		fields := m.Fields()
		idx, ok := fields.QNameMap[name]
		if !ok {
			return nil, fmt.Errorf("can't map field %q to a datastore name", name)
		}
		if strings.IndexByte(name, '.') >= 0 {
			// GAE flattens embedded fields, so we must remove
			// the parts of the field which refer to a flattened
			// field.
			indexes := fields.Indexes[idx]
			parts := strings.Split(name, ".")
			if len(indexes) == len(parts) {
				var final []string
				typ := fields.Type
				for ii, v := range indexes {
					f := typ.Field(v)
					if !f.Anonymous {
						final = append(final, parts[ii])
					}
					typ = f.Type
				}
				name = strings.Join(final, ".")
			}
		}
		log.Debugf("DATASTORE: filter %s %s %v", m, name+op, field.Value)
		dq = dq.Filter(name+op, field.Value)
	}
	return dq, nil
}