إنشاء التعليمات البرمجية في Go بمثال إنشاء عميل لقاعدة البيانات

في هذه المقالة ، أود النظر في قضايا إنشاء التعليمات البرمجية في Golang. لقد لاحظت أنه في كثير من الأحيان في التعليقات على المقالات على Go أذكر توليد التعليمات البرمجية والتفكير فيها ، مما يسبب جدلاً محتدماً. في الوقت نفسه ، هناك عدد قليل من المقالات حول إنشاء التعليمات البرمجية على المحور ، على الرغم من أنها تستخدم كثيرًا في المشاريع على Go. في المقالة سأحاول أن أخبرك ما هو توليد الكود ، لوصف نطاق التطبيق مع أمثلة الكود. أيضا ، لن أتجاهل التفكير.

عند استخدام توليد التعليمات البرمجية


على حبري هناك بالفعل مقالات جيدة حول هذا الموضوع هنا و هنا ، وأنا لن أكرر.

يجب استخدام توليد الشفرة في الحالات:

  • زيادة سرعة الشفرة ، أي استبدال الانعكاس ؛
  • تقليل روتين المبرمج (والأخطاء المرتبطة به) ؛
  • تنفيذ الأغلفة وفقًا للقواعد المحددة.

من الأمثلة ، يمكننا التفكير في مكتبة Stringer ، والتي يتم تضمينها في تزويد اللغة القياسية وتسمح لك بإنشاء طرق String () تلقائيًا لمجموعات الثوابت الرقمية. باستخدامه ، يمكنك تنفيذ إخراج أسماء المتغيرات. تم وصف أمثلة المكتبة بالتفصيل في المقالات أعلاه. كان المثال الأكثر إثارة للاهتمام هو اشتقاق اسم اللون من اللوحة. يتجنب تطبيق إنشاء الشفرة تغيير الرمز في عدة أماكن عند تغيير اللوحة.

من مثال عملي أكثر ، يمكننا ذكر مكتبة easyjson من Mail.ru. تسمح لك هذه المكتبة بتسريع تنفيذ masrshall / unmarshall JSON من / إلى الهيكل. تجاوز تنفيذها على المعايير جميع البدائل. لاستخدام المكتبة ، تحتاج إلى الاتصال بـ easyjson ، وستقوم بإنشاء رمز لجميع الهياكل التي تعثر عليها في الملف المنقول ، أو فقط لتلك التي تم التعليق عليها على // easyjson: json. خذ بنية المستخدم كمثال:

type User struct{
    ID int
    Login string
    Email string
    Level int
}

بالنسبة للملف الذي يحتوي عليه ، قم بتشغيل إنشاء التعليمات البرمجية:

easyjson -all main.go

نتيجة لذلك ، نحصل على طرق للمستخدم:

  • MarshalEasyJSON (w * jwriter.Writer) - لتحويل البنية إلى صفيف بايت JSON ؛
  • UnmarshalEasyJSON (l * jlexer.Lexer) - للتحويل من مجموعة من وحدات البايت إلى بنية.

دالات MarshalJSON () ([] بايت ، خطأ) و UnmarshalJSON (بيانات [] بايت) ضرورية للتوافق مع واجهة json القياسية.

كود Easyjson

func TestEasyJSON() {
	testJSON := `{"ID":123, "Login":"TestUser", "Email":"user@gmail.com", "Level":12}`
	JSONb := []byte(testJSON)
	fmt.Println(testJSON)
	recvUser := &User{}
	recvUser.UnmarshalJSON(JSONb)
	fmt.Println(recvUser)
	recvUser.Level += 1
	outJSON, _ := recvUser.MarshalJSON()
	fmt.Println(string(outJSON))
}


في هذه الوظيفة ، نقوم أولاً بتحويل JSON إلى هيكل ، وإضافة مستوى واحد وطباعة JSON الناتجة. يعني إنشاء الشفرة بواسطة easyjson التخلص من انعكاس وقت التشغيل وزيادة أداء الشفرة.

يتم استخدام إنشاء الشفرة بنشاط لإنشاء خدمات صغيرة تتواصل عبر gRPC. يستخدم تنسيق protobuf لوصف طرق الخدمات - باستخدام اللغة المتوسطة EDL. بعد وصف الخدمة ، يتم إطلاق برنامج التحويل البروتوني ، الذي يولد رمزًا للغة البرمجة المطلوبة. في الكود الذي تم إنشاؤه ، نحصل على الواجهات التي يجب تنفيذها في الخادم والأساليب المستخدمة على العميل لتنظيم الاتصال. اتضح بشكل ملائم تمامًا ، يمكننا وصف خدماتنا بتنسيق واحد وإنشاء رمز للغة البرمجة التي سيتم فيها وصف كل عنصر من عناصر التفاعل.

أيضا ، يمكن استخدام توليد التعليمات البرمجية في تطوير الأطر. على سبيل المثال ، لتنفيذ التعليمات البرمجية التي لا يشترط أن يكتبها مطور التطبيق ، ولكنها ضرورية للتشغيل الصحيح. على سبيل المثال ، لإنشاء أداة التحقق من حقل النموذج ، الإنشاء التلقائي للبرامج الوسيطة ، الإنشاء الديناميكي للعملاء لنظام DBMS.

تطبيق Go Code Generator


دعونا نفحص عمليًا كيفية عمل آلية إنشاء التعليمات البرمجية في Go. بادئ ذي بدء ، من الضروري ذكر AST - Abstract Syntax Tree أو Abstract Syntax Tree. لمزيد من التفاصيل ، يمكنك الذهاب إلى ويكيبيديا . من أجل أغراضنا ، من الضروري أن نفهم أن البرنامج بأكمله مبني على شكل رسم بياني ، حيث يتم تعيين القمم (المحددة) مع عوامل تشغيل لغة البرمجة ، والأوراق مع المعاملات المقابلة.

لذا ، بالنسبة للمبتدئين ، نحتاج إلى الحزم:

go / ast
go / parser /
go / token / يتم

تحليل الملف باستخدام الشفرة وتجميع الشجرة بواسطة الأوامر التالية


fset := token.NewFileSet()
node, err := parser.ParseFile(fset, os.Args[1], nil, parser.ParseComments)

نشير إلى أنه يجب أخذ اسم الملف من الوسيطة الأولى لسطر الأوامر ، ونطلب أيضًا إضافة التعليقات إلى الشجرة.

بشكل عام ، للتحكم في إنشاء الشفرة ، يمكن للمستخدم (مطور الشفرة الذي يتم إنشاء رمز آخر على أساسه) استخدام التعليقات أو العلامات (كما نكتب "json:" "` `بالقرب من حقل الهيكل).

على سبيل المثال ، سنكتب منشئ التعليمات البرمجية للعمل مع قاعدة بيانات. سينظر منشئ الشفرة في الملف الذي تم نقله إليه ، ويبحث عن الهياكل التي تحتوي على تعليق مطابق وإنشاء غلاف فوق البنية (طرق CRUD) لتفاعل قاعدة البيانات. سنستخدم المعلمات:

  • تعليق dbe: {"table": "users"} ، حيث يمكنك تحديد الجدول الذي ستكون فيه سجلات الهيكل ؛
  • علامة dbe لحقول البنية ، حيث يمكنك تحديد اسم العمود الذي سيتم فيه وضع قيمة الحقل وسمات قاعدة البيانات: basic_key و not_null. سيتم استخدامها عند إنشاء الجدول. واسم الحقل ، يمكنك استخدام "-" حتى لا يتم إنشاء عمود له.

سأبدي تحفظًا مقدمًا على أن المشروع لم يقاتل بعد ، ولن يحتوي على جزء من عمليات الفحص والحماية اللازمة. إذا كان هناك اهتمام ، فسأواصل تطويره.

لذا ، قررنا المهمة والمعلمات للتحكم في إنشاء التعليمات البرمجية ، يمكننا البدء في كتابة التعليمات البرمجية.

ستكون الروابط لجميع التعليمات البرمجية في نهاية المقالة.

نبدأ بتجاوز الشجرة الناتجة وسنقوم بتحليل كل عنصر من المستوى الأول. يحتوي Go على أنواع محددة مسبقًا للتحليل: BadDecl و GenDecl و FuncDecl.

اكتب الوصف
// A BadDecl node is a placeholder for declarations containing
// syntax errors for which no correct declaration nodes can be
// created.
//
BadDecl struct {
    From, To token.Pos // position range of bad declaration
}
// A GenDecl node (generic declaration node) represents an import,
// constant, type or variable declaration. A valid Lparen position
// (Lparen.IsValid()) indicates a parenthesized declaration.
//
// Relationship between Tok value and Specs element type:
//
// token.IMPORT *ImportSpec
// token.CONST *ValueSpec
// token.TYPE *TypeSpec
// token.VAR *ValueSpec
//
GenDecl struct {
    Doc *CommentGroup // associated documentation; or nil
    TokPos token.Pos // position of Tok
    Tok token.Token // IMPORT, CONST, TYPE, VAR
    Lparen token.Pos // position of '(', if any
    Specs []Spec
    Rparen token.Pos // position of ')', if any
}
// A FuncDecl node represents a function declaration.
FuncDecl struct {
    Doc *CommentGroup // associated documentation; or nil
    Recv *FieldList // receiver (methods); or nil (functions)
    Name *Ident // function/method name
    Type *FuncType // function signature: parameters, results, and position of "func" keyword
    Body *BlockStmt // function body; or nil for external (non-Go) function
}


نحن مهتمون بالهياكل ، لذلك نستخدم GenDecl. في هذه المرحلة ، قد يكون FuncDecl مفيدًا ، حيث تكمن تعريفات الدوال وتلفها ، لكننا لا نحتاجها الآن. بعد ذلك ، ننظر إلى صفيف المواصفات في كل عقدة ، ونبحث عن أننا نعمل مع حقل تعريف النوع (* ast.TypeSpec) وهذا هيكل (* ast.StructType). بعد أن قررنا أن لدينا هيكلًا ، نتحقق من أنه يحتوي على تعليق // dbe. فيما يلي رمز اجتياز الشجرة بالكامل وتعريف أي هيكل للعمل معه.

اجتياز الأشجار والحصول على الهياكل

for _, f := range node.Decls {
	genD, ok := f.(*ast.GenDecl)
	if !ok {
		fmt.Printf("SKIP %T is not *ast.GenDecl\n", f)
		continue
	}
	targetStruct := &StructInfo{}
	var thisIsStruct bool
	for _, spec := range genD.Specs {
		currType, ok := spec.(*ast.TypeSpec)
		if !ok {
			fmt.Printf("SKIP %T is not ast.TypeSpec\n", spec)
			continue
		}

		currStruct, ok := currType.Type.(*ast.StructType)
		if !ok {
			fmt.Printf("SKIP %T is not ast.StructType\n", currStruct)
			continue
		}
		targetStruct.Name = currType.Name.Name
		thisIsStruct = true
	}
	//Getting comments
	var needCodegen bool
	var dbeParams string
	if thisIsStruct {
		for _, comment := range genD.Doc.List {
			needCodegen = needCodegen || strings.HasPrefix(comment.Text, "// dbe")
			if len(comment.Text) < 7 {
				dbeParams = ""
			} else {
				dbeParams = strings.Replace(comment.Text, "// dbe:", "", 1)
			}
		}
	}
	if needCodegen {
		targetStruct.Target = genD
		genParams := &DbeParam{}
		if len(dbeParams) != 0 {
			err := json.Unmarshal([]byte(dbeParams), genParams)
			if err != nil {
				fmt.Printf("Error encoding DBE params for structure %s\n", targetStruct.Name)
				continue
			}
		} else {
			genParams.TableName = targetStruct.Name
		}

		targetStruct.GenParam = genParams
		generateMethods(targetStruct, out)
	}
}

, :

type DbeParam struct {
	TableName string `json:"table"`
}

type StructInfo struct {
	Name     string
	GenParam *DbeParam
	Target   *ast.GenDecl
}


الآن سنقوم بإعداد معلومات حول مجالات الهيكل ، بحيث ، بناءً على المعلومات الواردة ، سننشئ وظائف إنشاء الجدول (createTable) وأساليب CRUD.

كود للحصول على الحقول من هيكل

func generateMethods(reqStruct *StructInfo, out *os.File) {
	for _, spec := range reqStruct.Target.Specs {
		fmt.Fprintln(out, "")
		currType, ok := spec.(*ast.TypeSpec)
		if !ok {
			continue
		}
		currStruct, ok := currType.Type.(*ast.StructType)
		if !ok {
			continue
		}

		fmt.Printf("\tgenerating createTable methods for %s\n", currType.Name.Name)

		curTable := &TableInfo{
			TableName: reqStruct.GenParam.TableName,
			Columns:   make([]*ColInfo, 0, len(currStruct.Fields.List)),
		}

		for _, field := range currStruct.Fields.List {
			if len(field.Names) == 0 {
				continue
			}
			tableCol := &ColInfo{FieldName: field.Names[0].Name}
			var fieldIsPrimKey bool
			var preventThisField bool
			if field.Tag != nil {
				tag := reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1])
				tagVal := tag.Get("dbe")
				fmt.Println("dbe:", tagVal)
				tagParams := strings.Split(tagVal, ",")
			PARAMSLOOP:
				for _, param := range tagParams {
					switch param {
					case "primary_key":
						if curTable.PrimaryKey == nil {
							fieldIsPrimKey = true
							tableCol.NotNull = true
						} else {
							log.Panicf("Table %s cannot have more then 1 primary key!", currType.Name.Name)
						}
					case "not_null":
						tableCol.NotNull = true
					case "-":
						preventThisField = true
						break PARAMSLOOP
					default:
						tableCol.ColName = param
					}

				}
				if preventThisField {
					continue
				}
			}
			if tableCol.ColName == "" {
				tableCol.ColName = tableCol.FieldName
			}
			if fieldIsPrimKey {
				curTable.PrimaryKey = tableCol
			}
			//Determine field type
			var fieldType string
			switch field.Type.(type) {
			case *ast.Ident:
				fieldType = field.Type.(*ast.Ident).Name
			case *ast.SelectorExpr:
				fieldType = field.Type.(*ast.SelectorExpr).Sel.Name
			}
			//fieldType := field.Type.(*ast.Ident).Name
			fmt.Printf("%s- %s\n", tableCol.FieldName, fieldType)
			//Check for integers
			if strings.Contains(fieldType, "int") {
				tableCol.ColType = "integer"
			} else {
				//Check for other types
				switch fieldType {
				case "string":
					tableCol.ColType = "text"
				case "bool":
					tableCol.ColType = "boolean"
				case "Time":
					tableCol.ColType = "TIMESTAMP"
				default:
					log.Panicf("Field type %s not supported", fieldType)
				}
			}
			tableCol.FieldType = fieldType
			curTable.Columns = append(curTable.Columns, tableCol)
			curTable.StructName = currType.Name.Name

		}
		curTable.generateCreateTable(out)

		fmt.Printf("\tgenerating CRUD methods for %s\n", currType.Name.Name)
		curTable.generateCreate(out)
		curTable.generateQuery(out)
		curTable.generateUpdate(out)
		curTable.generateDelete(out)
	}
}


نذهب من خلال جميع حقول الهيكل المطلوب ونبدأ في تحليل علامات كل حقل. باستخدام الانعكاس ، نحصل على العلامة التي نهتم بها (بعد كل شيء ، قد تكون هناك علامات أخرى في الحقل ، على سبيل المثال ، لـ json). نحن نحلل محتويات العلامة ونحدد ما إذا كان الحقل هو مفتاح أساسي (إذا تم تحديد أكثر من مفتاح أساسي ، ولعن حوله ووقف التنفيذ) ، هل هناك حاجة إلى أن يكون الحقل غير صفري ، فهل نحتاج إلى العمل مع قاعدة البيانات لهذا الحقل وتحديد اسم العمود إذا تم تجاوزه في العلامة. نحتاج أيضًا إلى تحديد نوع عمود الجدول بناءً على نوع حقل الهيكل. هناك مجموعة محدودة من أنواع الحقول ، وسننشئ فقط للأنواع الأساسية ، وسنقلل جميع الصفوف إلى نوع حقل TEXT ، على الرغم بشكل عام ، يمكنك إضافة تعريف نوع العمود إلى العلامات بحيث يمكنك التخصيص بشكل أكثر دقة. من ناحية أخرى،لا يزعج أحد بإنشاء الجدول المطلوب في قاعدة البيانات مقدمًا ، أو لتصحيح الجدول الذي تم إنشاؤه تلقائيًا.

بعد تحليل الهيكل ، نبدأ طريقة إنشاء التعليمات البرمجية لوظيفة إنشاء الجدول وطرق إنشاء وظائف الإنشاء والاستعلام والتحديث والحذف. نحن نعد تعبير SQL لكل دالة وملزمة للتشغيل. لم أكن منزعجًا من معالجة الخطأ ، ولكنني أعطي الخطأ من برنامج تشغيل قاعدة البيانات. لإنشاء التعليمات البرمجية ، من المناسب استخدام القوالب من مكتبة النصوص / القوالب. بمساعدتهم ، يمكنك الحصول على رمز أكثر دعمًا وقابلية للتنبؤ (الرمز مرئي على الفور ، ولكن لا يلطخه كود المولد).

إنشاء الجدول

func (tableD *TableInfo) generateCreateTable(out *os.File) error {
	fmt.Fprint(out, "func (in *"+tableD.StructName+") createTable(db *sql.DB) (error) {\n")
	var resSQLq = fmt.Sprintf("\tsqlQ := `CREATE TABLE %s (\n", tableD.TableName)
	for _, col := range tableD.Columns {
		colSQL := col.ColName + " " + col.ColType
		if col.NotNull {
			colSQL += " NOT NULL"
		}
		if col == tableD.PrimaryKey {
			colSQL += " AUTO_INCREMENT"
		}
		colSQL += ",\n"
		resSQLq += colSQL
	}
	if tableD.PrimaryKey != nil {
		resSQLq += fmt.Sprintf("PRIMARY KEY (%s)\n", tableD.PrimaryKey.ColName)
	}
	resSQLq += ")`\n"
	fmt.Fprint(out, resSQLq)
	fmt.Fprint(out, "\t_, err := db.Exec(sqlQ)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n")
	fmt.Fprint(out, "\t return nil\n}\n\n")
	return nil
}


إضافة سجل


	fmt.Fprint(out, "func (in *"+tableD.StructName+") Create(db *sql.DB) (error) {\n")
	var columns, valuePlaces, valuesListParams string
	for _, col := range tableD.Columns {
		if col == tableD.PrimaryKey {
			continue
		}
		columns += "`" + col.ColName + "`,"
		valuePlaces += "?,"
		valuesListParams += "in." + col.FieldName + ","
	}
	columns = columns[:len(columns)-1]
	valuePlaces = valuePlaces[:len(valuePlaces)-1]
	valuesListParams = valuesListParams[:len(valuesListParams)-1]

	resSQLq := fmt.Sprintf("\tsqlQ := \"INSERT INTO %s (%s) VALUES (%s);\"\n",
		tableD.TableName,
		columns,
		valuePlaces)
	fmt.Fprintln(out, resSQLq)
	fmt.Fprintf(out, "result, err := db.Exec(sqlQ, %s)\n", valuesListParams)
	fmt.Fprintln(out, `if err != nil {
		return err
	}`)
	//Setting id if we have primary key
	if tableD.PrimaryKey != nil {
		fmt.Fprintf(out, `lastId, err := result.LastInsertId()
		if err != nil {
			return nil
		}`)
		fmt.Fprintf(out, "\nin.%s = %s(lastId)\n", tableD.PrimaryKey.FieldName, tableD.PrimaryKey.FieldType)
	}
	fmt.Fprintln(out, "return nil\n}\n\n")
	//in., _ := result.LastInsertId()`)
	return nil
}


استرجاع السجلات من جدول

func (tableD *TableInfo) generateQuery(out *os.File) error {
	fmt.Fprint(out, "func (in *"+tableD.StructName+") Query(db *sql.DB) ([]*"+tableD.StructName+", error) {\n")

	fmt.Fprintf(out, "\tsqlQ := \"SELECT * FROM %s;\"\n", tableD.TableName)
	fmt.Fprintf(out, "rows, err := db.Query(sqlQ)\n")
	fmt.Fprintf(out, "results := make([]*%s, 0)\n", tableD.StructName)
	fmt.Fprintf(out, `for rows.Next() {`)
	fmt.Fprintf(out, "\t tempR := &%s{}\n", tableD.StructName)
	var valuesListParams string
	for _, col := range tableD.Columns {
		valuesListParams += "&tempR." + col.FieldName + ","
	}
	valuesListParams = valuesListParams[:len(valuesListParams)-1]

	fmt.Fprintf(out, "\terr = rows.Scan(%s)\n", valuesListParams)
	fmt.Fprintf(out, `if err != nil {
		return nil, err
		}`)
	fmt.Fprintf(out, "\n\tresults = append(results, tempR)")
	fmt.Fprintf(out, `}
		return results, nil
	}`)
	fmt.Fprintln(out, "")
	fmt.Fprintln(out, "")
	return nil
}


تحديث السجل (يعمل بواسطة المفتاح الأساسي)

func (tableD *TableInfo) generateUpdate(out *os.File) error {
	fmt.Fprint(out, "func (in *"+tableD.StructName+") Update(db *sql.DB) (error) {\n")
	var updVals, valuesListParams string
	for _, col := range tableD.Columns {
		if col == tableD.PrimaryKey {
			continue
		}
		updVals += "`" + col.ColName + "`=?,"
		valuesListParams += "in." + col.FieldName + ","
	}
	updVals = updVals[:len(updVals)-1]
	valuesListParams += "in." + tableD.PrimaryKey.FieldName

	resSQLq := fmt.Sprintf("\tsqlQ := \"UPDATE %s SET %s WHERE %s = ?;\"\n",
		tableD.TableName,
		updVals,
		tableD.PrimaryKey.ColName)
	fmt.Fprintln(out, resSQLq)
	fmt.Fprintf(out, "_, err := db.Exec(sqlQ, %s)\n", valuesListParams)
	fmt.Fprintln(out, `if err != nil {
		return err
	}`)

	fmt.Fprintln(out, "return nil\n}\n\n")
	//in., _ := result.LastInsertId()`)
	return nil
}


حذف سجل (يعمل عن طريق المفتاح الأساسي)

func (tableD *TableInfo) generateDelete(out *os.File) error {
	fmt.Fprint(out, "func (in *"+tableD.StructName+") Delete(db *sql.DB) (error) {\n")
	fmt.Fprintf(out, "sqlQ := \"DELETE FROM %s WHERE id = ?\"\n", tableD.TableName)

	fmt.Fprintf(out, "_, err := db.Exec(sqlQ, in.%s)\n", tableD.PrimaryKey.FieldName)

	fmt.Fprintln(out, `if err != nil {
		return err
	}
	return nil
}`)
	fmt.Fprintln(out)
	return nil
}


يتم تنفيذ بداية منشئ الرمز الناتج عن طريق التشغيل المعتاد ، ونقوم بتمرير المسار إلى الملف الذي تريد إنشاء الرمز له في علامة -name. نتيجة لذلك ، نحصل على الملف باستخدام اللاحقة _dbe ، حيث يكمن الرمز الذي تم إنشاؤه. للاختبارات ، قم بإنشاء طرق للبنية التالية:


// dbe:{"table": "users"}
type User struct {
	ID       int    `dbe:"id,primary_key"`
	Login    string `dbe:"login,not_null"`
	Email    string
	Level    uint8
	IsActive bool
	UError   error `dbe:"-"`
}

الكود الناتج

package main

import "database/sql"

func (in *User) createTable(db *sql.DB) error {
	sqlQ := `CREATE TABLE users (
	id integer NOT NULL AUTO_INCREMENT,
	login text NOT NULL,
	Email text,
	Level integer,
	IsActive boolean,
	PRIMARY KEY (id)
	)`
	_, err := db.Exec(sqlQ)
	if err != nil {
		return err
	}
	return nil
}

func (in *User) Create(db *sql.DB) error {
	sqlQ := "INSERT INTO users (`login`,`Email`,`Level`,`IsActive`) VALUES (?,?,?,?);"

	result, err := db.Exec(sqlQ, in.Login, in.Email, in.Level, in.IsActive)
	if err != nil {
		return err
	}
	lastId, err := result.LastInsertId()
	if err != nil {
		return nil
	}
	in.ID = int(lastId)
	return nil
}

func (in *User) Query(db *sql.DB) ([]*User, error) {
	sqlQ := "SELECT * FROM users;"
	rows, err := db.Query(sqlQ)
	results := make([]*User, 0)
	for rows.Next() {
		tempR := &User{}
		err = rows.Scan(&tempR.ID, &tempR.Login, &tempR.Email, &tempR.Level, &tempR.IsActive)
		if err != nil {
			return nil, err
		}
		results = append(results, tempR)
	}
	return results, nil
}

func (in *User) Update(db *sql.DB) error {
	sqlQ := "UPDATE users SET `login`=?,`Email`=?,`Level`=?,`IsActive`=? WHERE id = ?;"

	_, err := db.Exec(sqlQ, in.Login, in.Email, in.Level, in.IsActive, in.ID)
	if err != nil {
		return err
	}
	return nil
}

func (in *User) Delete(db *sql.DB) error {
	sqlQ := "DELETE FROM users WHERE id = ?"
	_, err := db.Exec(sqlQ, in.ID)
	if err != nil {
		return err
	}
	return nil
}


لاختبار تشغيل التعليمات البرمجية التي تم إنشاؤها ، قم بإنشاء كائن ببيانات عشوائية ، قم بإنشاء جدول له (إذا كان الجدول موجودًا في قاعدة البيانات ، فسيتم إرجاع خطأ). بعد وضع هذا الكائن في الجدول ، اقرأ جميع الحقول من الجدول ، وقم بتحديث قيم المستوى وحذف الكائن.

استدعاء الأساليب الناتجة

var err error
db, err := sql.Open("mysql", DSN)
if err != nil {
	fmt.Println("Unable to connect to DB", err)
	return
}
err = db.Ping()
if err != nil {
	fmt.Println("Unable to ping BD")
	return
}
newUser := &User{
	Login:    "newUser",
	Email:    "new@test.com",
	Level:    0,
	IsActive: false,
	UError:   nil,
}

err = newUser.createTable(db)
if err != nil {
	fmt.Println("Error creating table.", err)

}
err = newUser.Create(db)
if err != nil {
	fmt.Println("Error creating user.", err)
	return
}

nU := &User{}
dbUsers, err := nU.Query(db)
if err != nil {
	fmt.Println("Error selecting users.", err)
	return
}
fmt.Printf("From table users selected %d fields", len(dbUsers))
var DBUser *User
for _, user := range dbUsers {
	fmt.Println(user)
	DBUser = user
}
DBUser.Level = 2
err = DBUser.Update(db)
if err != nil {
	fmt.Println("Error updating users.", err)
	return
}
err = DBUser.Delete(db)
if err != nil {
	fmt.Println("Error deleting users.", err)
	return
}


في التطبيق الحالي ، تكون وظائف العميل لقاعدة البيانات محدودة للغاية:

  • يتم دعم MySQL فقط ؛
  • لا يتم دعم جميع أنواع الحقول.
  • لا توجد تصفية وحدود لـ SELECT.

ومع ذلك ، فإن إصلاح الأخطاء يتجاوز بالفعل نطاق تحليل شفرة مصدر Go وإنشاء رمز جديد بناءً عليه.

سيسمح لك استخدام منشئ الكود في مثل هذا السيناريو بتغيير حقول وأنواع الهياكل المستخدمة في التطبيق في مكان واحد فقط ، ولا داعي لتذكر إجراء تغييرات على الكود للتفاعل مع قاعدة البيانات ، ما عليك سوى تشغيل منشئ الكود في كل مرة. يمكن حل هذه المهمة بمساعدة التفكير ، ولكن هذا قد يؤثر على الأداء.

مُنشئ رمز المصدر ومثال على التعليمات البرمجية التي تم إنشاؤها على Github .

All Articles