Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upsert()? #392

Open
mcandre opened this issue Apr 3, 2019 · 1 comment
Open

Upsert()? #392

mcandre opened this issue Apr 3, 2019 · 1 comment

Comments

@mcandre
Copy link

mcandre commented Apr 3, 2019

Could there be a method for performing an update-or-insert as needed?

@MasterDimmy
Copy link

MasterDimmy commented May 18, 2020

thats it, and its working

//upsert code (insert or update)
//i: to insert or update
//case update_filter==nil => all fields are updated
//conflict_filter = conflict column names
func (m *DbMap) Upsert(i interface{}, update_filter ColumnFilter, conflict_filter ColumnFilter) error {
	return upsert(m, m, i, update_filter, conflict_filter)
}
//inserts i as insert
//if exists updates columns as written in update_filter
//occurs on conflict on conflict_filter
func upsert(m *DbMap, exec SqlExecutor, ptr interface{}, update_filter ColumnFilter, conflict_filter ColumnFilter) error {
	if _, ok := m.Dialect.(PostgresDialect); !ok {
		return fmt.Errorf("not supported dialect!")
	}

	table, elem, err := m.tableForPointer(ptr, false)
	if err != nil {
		return err
	}

	eval := elem.Addr().Interface()
	//as insert
	if v, ok := eval.(HasPreInsert); ok {
		err := v.PreInsert(exec)
		if err != nil {
			return err
		}
	}
	//as update
	if v, ok := eval.(HasPreUpdate); ok {
		err = v.PreUpdate(exec)
		if err != nil {
			return err
		}
	}

	bi, err := table.bindUpsert(elem, update_filter, conflict_filter)
	if err != nil {
		return err
	}

	_, err = exec.Exec(bi.query, bi.args...)
	if err != nil {
		return err
	}

	//post actions
	//insert
	if v, ok := eval.(HasPostInsert); ok {
		err := v.PostInsert(exec)
		if err != nil {
			return err
		}
	}
	//update
	if v, ok := eval.(HasPostUpdate); ok {
		err = v.PostUpdate(exec)
		if err != nil {
			return err
		}
	}

	return nil
}

func (t *TableMap) bindUpsert(elem reflect.Value, colFilter ColumnFilter, conflictFilter ColumnFilter) (bindInstance, error) {
	if colFilter == nil {
		colFilter = acceptAllFilter
	}

	if conflictFilter == nil {
		return bindInstance{}, fmt.Errorf("conflictFilter cant be nill")
	}

	plan := &t.upsertPlan
	plan.once.Do(func() {
		plan.autoIncrIdx = -1

		s := bytes.Buffer{}
		s2 := bytes.Buffer{}
		s.WriteString(fmt.Sprintf("insert into %s (", t.dbmap.Dialect.QuotedTableForQuery(t.SchemaName, t.TableName)))

		x := 0
		first := true
		for y := range t.Columns {
			col := t.Columns[y]
			if !(col.isAutoIncr && t.dbmap.Dialect.AutoIncrBindValue() == "") {
				if !col.Transient {
					if !first {
						s.WriteString(",")
						s2.WriteString(",")
					}
					s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))

					if col.isAutoIncr {
						s2.WriteString(t.dbmap.Dialect.AutoIncrBindValue())
						plan.autoIncrIdx = y
						plan.autoIncrFieldName = col.fieldName
					} else {
						if col.DefaultValue == "" {
							s2.WriteString(t.dbmap.Dialect.BindVar(x))
							if col == t.version {
								plan.versField = col.fieldName
								plan.argFields = append(plan.argFields, versFieldConst)
							} else {
								plan.argFields = append(plan.argFields, col.fieldName)
							}
							x++
						} else {
							s2.WriteString(col.DefaultValue)
						}
					}
					first = false
				}
			} else {
				plan.autoIncrIdx = y
				plan.autoIncrFieldName = col.fieldName
			}
		}
		s.WriteString(") values (")
		s.WriteString(s2.String())
		s.WriteString(")")

		s.WriteString(" on conflict")

		s.WriteString("(")
		ci := 0
		for y := range t.Columns {
			col := t.Columns[y]
			if conflictFilter(col) {
				if ci > 0 {
					s.WriteString(", ")
				}
				s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
				ci++
			}
		}
		if ci == 0 {
			panic("conflictFilter is incorrect: no one column matched")
		}
		s.WriteString(")")

		s.WriteString(" do update set ")
		x2 := 0
		for y := range t.Columns {
			col := t.Columns[y]
			if !col.isAutoIncr && !col.Transient && colFilter(col) {
				if x2 > 0 {
					s.WriteString(", ")
				}
				s.WriteString(t.dbmap.Dialect.QuoteField(col.ColumnName))
				s.WriteString("=")
				s.WriteString(t.dbmap.Dialect.BindVar(x + x2))

				if col == t.version {
					plan.versField = col.fieldName
					plan.argFields = append(plan.argFields, versFieldConst)
				} else {
					plan.argFields = append(plan.argFields, col.fieldName)
				}
				x2++
			}
		}

		s.WriteString(t.dbmap.Dialect.QuerySuffix())

		plan.query = s.String()
	})

	return plan.createBindInstance(elem, t.dbmap.TypeConverter)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants