geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

动手写ORM框架 - GeeORM第三天 记录新增和查询 | 极客兔兔 #79

Open geektutu opened 4 years ago

geektutu commented 4 years ago

https://geektutu.com/post/geeorm-day3.html

7天用 Go语言/golang 从零实现 ORM 框架 GeeORM 教程(7 days implement golang object relational mapping framework from scratch tutorial),动手写 ORM 框架,参照 gorm, xorm 的实现。实现新增(insert)记录的功能;使用反射(reflect)将数据库的记录转换为对应的结构体实例,实现查询(select)功能。

walkmiao commented 4 years ago
for i, value := range values {
        v := value.([]interface{})
        if bindStr == "" {
            bindStr = genBindVars(len(v))
        }
        sql.WriteString(fmt.Sprintf("(%v)", bindStr))
        if i+1 != len(values) {
            sql.WriteString(", ")
        }
        vars = append(vars, v...)
    }

value.([]interface{})这里是什么意图没看懂?能解释下吗为什么是一个空接口的切片呢

walkmiao commented 4 years ago

我在一个测试文件中看到TestMain方法 那里面有一句 _ = TestDB.Close() 为什么db close后紧接着的测试还能正常进行呢 DB不是已经Close了吗

geektutu commented 4 years ago

@walkmiao

我在一个测试文件中看到TestMain方法 那里面有一句 _ = TestDB.Close() 为什么db close后紧接着的测试还能正常进行呢 DB不是已经Close了吗

func TestMain(m *testing.M) {
    TestDB, _ = sql.Open("sqlite3", "../gee.db")
    code := m.Run()
    _ = TestDB.Close()
    os.Exit(code)
}

这个是 Go 测试工程的 setupteardown 的机制,允许在每个用例运行m.Run()前后做一些事情,Open 是用例执行前运行的,Close 是用例执行后运行的。

参考 Go语言单元测试简明教程#setup 和 teardown

walkmiao commented 4 years ago

谢谢我去学习下 lch 邮箱:372815340@qq.com 签名由 网易邮箱大师 定制 在2020年04月03日 11:57,Dai Jie 写道: @walkmiao 我在一个测试文件中看到TestMain方法 那里面有一句 _ = TestDB.Close() 为什么db close后紧接着的测试还能正常进行呢 DB不是已经Close了吗 func TestMain(m *testing.M) {

TestDB, _ = sql.Open("sqlite3", "../gee.db")

code := m.Run()

_ = TestDB.Close()

os.Exit(code)

} 这个是 Go 测试工程的 setup 和 teardown 的机制,允许在每个用例运行m.Run()前后做一些事情,Open 是用例执行前运行的,Close 是用例执行后运行的。 参考 Go语言单元测试简明教程#setup 和 teardown — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

yoursNicholas commented 3 years ago

Find 的代码,真的看不懂,咋整

furthergo commented 3 years ago

@walkmiao

for i, value := range values {
      v := value.([]interface{})
      if bindStr == "" {
          bindStr = genBindVars(len(v))
      }
      sql.WriteString(fmt.Sprintf("(%v)", bindStr))
      if i+1 != len(values) {
          sql.WriteString(", ")
      }
      vars = append(vars, v...)
  }

value.([]interface{})这里是什么意图没看懂?能解释下吗为什么是一个空接口的切片呢

每个Model的RecordValues都是一个切片,这里个values是一个二维数组

furthergo commented 3 years ago
func (s *Session) Insert(values ...interface{}) (int64, error) {
    recordValues := make([]interface{}, 0)
    for _, value := range values {
        table := s.Model(value).RefTable()
        s.clause.Set(clause.INSERT, table.Name, table.FieldNames)
        recordValues = append(recordValues, table.RecordValues(value))
    }

    s.clause.Set(clause.VALUES, recordValues...)
    sql, vars := s.clause.Build(clause.INSERT, clause.VALUES)
    result, err := s.Raw(sql, vars...).Exec()
    if err != nil {
        return 0, err
    }

    return result.RowsAffected()
}

请教一下,这里s.clause.Set(clause.INSERT, table.Name, table.FieldNames)是不是只用设置一次,同一个Type一次执行应该只能设置一次query string吧,这里是为了书写方便是吗?

geektutu commented 3 years ago

@furthergo 你的理解是对的,table.Name 只需要设置一次,的确是为了书写方便。

xiezhenyu19970913 commented 3 years ago

type generator func(values ...interface{}) (string, []interface{})

var generators map[Type]generator 请问这段是什么意思,函数作为MAP的VALUE吗?

geektutu commented 3 years ago

@xiezhenyu19970913 对的,把函数看做普通变量来使用即可。

kekemuyu commented 3 years ago

看到geeorm这一章感觉有点吃力,主要是对反射和函数型编程不熟

tmphuang6 commented 3 years ago

func (s *Session) Find(values interface{}) error { destSlice := reflect.Indirect(reflect.ValueOf(values)) destType := destSlice.Type().Elem() table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

会不会这样写 table := s.Model(reflect.New(destType).Interface()).RefTable() 好点

BrianQy commented 2 years ago

先介绍generator,然后当中的map[Type]generator的Type会比较让人费解,通过github代码cmd+A发现实际上Type定义在clause里面,但是介绍顺序导致读者理解起来比较尴尬。

DurantVivado commented 2 years ago

这就是大佬在Top公司一天的任务量吗,换做是我可能要一周。没有比较,就没有暴击!

qixiasu commented 2 years ago

太难了😂

qixiasu commented 2 years ago

看来不把反射搞清楚是看不下去了……

niconical commented 1 year ago

@tmphuang6 func (s *Session) Find(values interface{}) error { destSlice := reflect.Indirect(reflect.ValueOf(values)) destType := destSlice.Type().Elem() table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

会不会这样写 table := s.Model(reflect.New(destType).Interface()).RefTable() 好点

不会,因为需要destSlice,destType

ShiMaRing commented 1 year ago

@tmphuang6 func (s *Session) Find(values interface{}) error { destSlice := reflect.Indirect(reflect.ValueOf(values)) destType := destSlice.Type().Elem() table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

会不会这样写 table := s.Model(reflect.New(destType).Interface()).RefTable() 好点

不太行,reflect.New注释里面 New returns a Value representing a pointer to a new zero value ,the returned Value's Type is PointerTo(typ),给的是一个指针的Value,需要使用Elem方法提取具体的值

Omari-00 commented 1 year ago

Find方法里给values传递dest各个field的指针然后通过scan(values...)这样间接给dest赋值这个方法感觉很巧妙。

bowenddd commented 1 year ago
    for i, value := range values {
        v := value.([]interface{})
        if bindStr == "" {
            bindStr = genBindVars(len(v))
        }
        sql.WriteString(fmt.Sprintf("(%v)", bindStr))
        if i+1 != len(values) {
            sql.WriteString(", ")
        }
        vars = append(vars, v...)
    }

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

drone789 commented 1 year ago

@bowenddd

  for i, value := range values {
      v := value.([]interface{})
      if bindStr == "" {
          bindStr = genBindVars(len(v))
      }
      sql.WriteString(fmt.Sprintf("(%v)", bindStr))
      if i+1 != len(values) {
          sql.WriteString(", ")
      }
      vars = append(vars, v...)
  }

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

v := value.([]interface{})      // 将value转成了切片,切片长度只有1
bindStr = genBindVars(len(v))   // len(v) = 1, bindStr = (?)

这里应该没有问题。
drone789 commented 1 year ago
func (s *Session) Find(values interface{}) error {
    destSlice := reflect.Indirect(reflect.ValueOf(values))
    destType := destSlice.Type().Elem()
    table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

    s.clause.Set(clause.SELECT, table.Name, table.FieldNames)
    sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
    rows, err := s.Raw(sql, vars...).QueryRows()
    if err != nil {
        return err
    }

    for rows.Next() {
        dest := reflect.New(destType).Elem()
        var values []interface{}
        for _, name := range table.FieldNames {
            values = append(values, dest.FieldByName(name).Addr().Interface())
        }
        if err := rows.Scan(values...); err != nil {
            return err
        }
        destSlice.Set(reflect.Append(destSlice, dest))
    }
    return rows.Close()
}

destSlice.Set(reflect.Append(destSlice, dest)) 每次循环Set一次,能否改成循环结束后,一次Set?

paopaoshuaige commented 1 year ago

@bowenddd

  for i, value := range values {
      v := value.([]interface{})
      if bindStr == "" {
          bindStr = genBindVars(len(v))
      }
      sql.WriteString(fmt.Sprintf("(%v)", bindStr))
      if i+1 != len(values) {
          sql.WriteString(", ")
      }
      vars = append(vars, v...)
  }

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

是的 我也检测出了这个问题 我改了一下

func _values(values ...interface{}) (string, []interface{}) {
    // VALUES ($v1), ($v2), ...
    var bindStr string
    var sql strings.Builder
    var vars []interface{}
    sql.WriteString("VALUES ")
    for i, value := range values {
        v := value.([]interface{}) // 将value转成了切片,切片长度只有1
        // 转换成对应v切片数量的问号
        bindStr = genBindVars(len(v))
        sql.WriteString(fmt.Sprintf("(%v)", bindStr))
        if i+1 != len(values) { // 不结束就分割一下
            sql.WriteString(", ")
        }
        vars = append(vars, v...)
    }
    return sql.String(), vars
}
paopaoshuaige commented 1 year ago

@drone789

func (s *Session) Find(values interface{}) error {
  destSlice := reflect.Indirect(reflect.ValueOf(values))
  destType := destSlice.Type().Elem()
  table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

  s.clause.Set(clause.SELECT, table.Name, table.FieldNames)
  sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
  rows, err := s.Raw(sql, vars...).QueryRows()
  if err != nil {
      return err
  }

  for rows.Next() {
      dest := reflect.New(destType).Elem()
      var values []interface{}
      for _, name := range table.FieldNames {
          values = append(values, dest.FieldByName(name).Addr().Interface())
      }
      if err := rows.Scan(values...); err != nil {
          return err
      }
      destSlice.Set(reflect.Append(destSlice, dest))
  }
  return rows.Close()
}

destSlice.Set(reflect.Append(destSlice, dest)) 每次循环Set一次,能否改成循环结束后,一次Set?

不可以 一个dest是一个对应的结构体而不是切片,不能存储多个值,只能一次一次往里加

a2cd commented 1 year ago

@paopaoshuaige

@bowenddd

    for i, value := range values {
        v := value.([]interface{})
        if bindStr == "" {
            bindStr = genBindVars(len(v))
        }
        sql.WriteString(fmt.Sprintf("(%v)", bindStr))
        if i+1 != len(values) {
            sql.WriteString(", ")
        }
        vars = append(vars, v...)
    }

_value函数中for循环中是否应该将关于bindStr的判断去掉呢?如果有if判断的话只会在第一个循环中给bindStr赋值,但是每个value的长度应该是不同的吧?

是的 我也检测出了这个问题 我改了一下

func _values(values ...interface{}) (string, []interface{}) {
  // VALUES ($v1), ($v2), ...
  var bindStr string
  var sql strings.Builder
  var vars []interface{}
  sql.WriteString("VALUES ")
  for i, value := range values {
      v := value.([]interface{}) // 将value转成了切片,切片长度只有1
      // 转换成对应v切片数量的问号
      bindStr = genBindVars(len(v))
      sql.WriteString(fmt.Sprintf("(%v)", bindStr))
      if i+1 != len(values) { // 不结束就分割一下
          sql.WriteString(", ")
      }
      vars = append(vars, v...)
  }
  return sql.String(), vars
}

这里没问题,只是实现的方式不同。s.Insert这样写想达到的目的是批量插入同类型多笔数据。
INSERT INTO table (c1,c2,c3) VALUES(1,2,3),(1,2,3)

如果想一次插入多种类型的多笔数据的话需要改造一下,类似于

func (s *Session) Insert(values ...interface{}) (int64, error) {
    for _, value := range values {
        table := s.Model(value).RefTable()
        s.clause.Set(clause.INSERT, table.Name, table.FieldNames)

        // 类似于这样改,思路是这样
        s.clause.Set(clause.VALUES, table.RecordValues(value))
        sql, vars := s.clause.Build(clause.INSERT, clause.VALUES)
        result, err := s.Raw(sql, vars...).Exec()
        if err != nil {
            return 0, err
        }
    }
    ......
    return result.RowsAffected()
}

这样每个value都会反射,SQL都会有一个INSERT开头
s.Insert(&User{}, &Student{}) 可以这样用,但是不推荐。
INSERT INTO table1 (c1,c2,c3) VALUES(1,2,3)
INSERT INTO table2 (c1,c2) VALUES(1,2)

GuiQuQu commented 1 year ago

@drone789

func (s *Session) Find(values interface{}) error {
  destSlice := reflect.Indirect(reflect.ValueOf(values))
  destType := destSlice.Type().Elem()
  table := s.Model(reflect.New(destType).Elem().Interface()).RefTable()

  s.clause.Set(clause.SELECT, table.Name, table.FieldNames)
  sql, vars := s.clause.Build(clause.SELECT, clause.WHERE, clause.ORDERBY, clause.LIMIT)
  rows, err := s.Raw(sql, vars...).QueryRows()
  if err != nil {
      return err
  }

  for rows.Next() {
      dest := reflect.New(destType).Elem()
      var values []interface{}
      for _, name := range table.FieldNames {
          values = append(values, dest.FieldByName(name).Addr().Interface())
      }
      if err := rows.Scan(values...); err != nil {
          return err
      }
      destSlice.Set(reflect.Append(destSlice, dest))
  }
  return rows.Close()
}

destSlice.Set(reflect.Append(destSlice, dest)) 每次循环Set一次,能否改成循环结束后,一次Set?

这个不是挺正常的吗,dest保存一行查询数据赋值给相应结构体的值,每次赋好一个值就向slice里面append一下

hjrbill commented 5 months ago
    for rows.Next() {
        dest := reflect.New(destType).Elem()
        var values []interface{}
        for _, name := range table.FieldNames {
            values = append(values, dest.FieldByName(name).Addr().Interface())
        }
        if err := rows.Scan(values...); err != nil {
            // 此处
            return err
        }
        destSlice.Set(reflect.Append(destSlice, dest))
    }

大佬们,为什么上面的“此处”报错返回时,不做rows.Close()?