डेटाबेस के लिए एक क्लाइंट बनाने के उदाहरण से गो में कोड पीढ़ी

इस लेख में, मैं गोलंग में कोड पीढ़ी के मुद्दों पर विचार करना चाहूंगा। मैंने देखा कि अक्सर गो कोड पीढ़ी और प्रतिबिंब पर लेखों पर टिप्पणियों में, जो गर्म बहस का कारण बनता है। इसी समय, हब पर कोड पीढ़ी पर कुछ लेख हैं, हालांकि यह गो पर परियोजनाओं में काफी उपयोग किया जाता है। लेख में मैं आपको यह बताने की कोशिश करूंगा कि कोड के उदाहरण के साथ आवेदन के दायरे का वर्णन करने के लिए कोड पीढ़ी क्या है। इसके अलावा, मैं प्रतिबिंब को नजरअंदाज नहीं करूंगा।

जब कोड पीढ़ी का उपयोग किया जाता है


Habré पर पहले से ही यहाँ और यहाँ के विषय पर अच्छे लेख हैं , मैं नहीं दोहराऊंगा।

कोड पीढ़ी का उपयोग मामलों में किया जाना चाहिए:

  • कोड की गति बढ़ाना, अर्थात् प्रतिबिंब को बदलना;
  • एक प्रोग्रामर की दिनचर्या को कम करना (और इससे जुड़ी त्रुटियाँ);
  • दिए गए नियमों के अनुसार रैपर का कार्यान्वयन।

उदाहरणों से, हम स्ट्रिंगर लाइब्रेरी पर विचार कर सकते हैं, जो मानक भाषा की आपूर्ति में शामिल है और आपको संख्यात्मक स्थिरांक के सेट के लिए स्ट्रिंग () विधियों को स्वचालित रूप से उत्पन्न करने की अनुमति देता है। इसका उपयोग करते हुए, आप चर नामों के आउटपुट को लागू कर सकते हैं। उपरोक्त लेखों में पुस्तकालय के उदाहरणों का विस्तार से वर्णन किया गया था। सबसे दिलचस्प उदाहरण पैलेट से रंग के नाम की व्युत्पत्ति थी। वहाँ कोड पीढ़ी के अनुप्रयोग को पैलेट बदलते समय कई स्थानों पर कोड को बदलने से बचा जाता है।

एक अधिक व्यावहारिक उदाहरण से, हम मेलजोन से ईजीसन लाइब्रेरी का उल्लेख कर सकते हैं। यह लाइब्रेरी आपको संरचना से / तक मेर्सहॉल / अनमरशॉल JSON के निष्पादन में तेजी लाने की अनुमति देती है। बेंचमार्क पर उनके कार्यान्वयन ने सभी विकल्पों को दरकिनार कर दिया। पुस्तकालय का उपयोग करने के लिए, आपको आसानी से कॉल करने की आवश्यकता है, यह सभी संरचनाओं के लिए कोड उत्पन्न करेगा जो इसे स्थानांतरित फ़ाइल में पाता है, या केवल उन लोगों के लिए जिनके लिए टिप्पणी // 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) - एक बाइट्स की एक सरणी से एक संरचना में परिवर्तित करने के लिए।

मार्शल जेन्सन () ([] बाइट, त्रुटि) और अनमरशालजसन (डेटा [] बाइट) त्रुटि कार्य मानक जोंस इंटरफेस के साथ संगतता के लिए आवश्यक हैं।

इज्जसन कोड

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 प्रिंट करते हैं। ईजीजोन द्वारा कोड जेनरेशन का मतलब रनटाइम रिफ्लेक्शन से छुटकारा पाने और कोड परफॉर्मेंस बढ़ाने से है।

कोड पीढ़ी को सक्रिय रूप से माइक्रोसर्विसेज बनाने के लिए उपयोग किया जाता है जो जीआरपीसी के माध्यम से संचार करते हैं। यह सेवाओं के तरीकों का वर्णन करने के लिए प्रोटोबोफ़ प्रारूप का उपयोग करता है - मध्यवर्ती भाषा ईडीएल का उपयोग करना। सेवा के विवरण के बाद, प्रोटो कंपाइलर लॉन्च किया गया है, जो वांछित प्रोग्रामिंग भाषा के लिए कोड उत्पन्न करता है। उत्पन्न कोड में, हमें उन इंटरफेस मिलते हैं जिन्हें सर्वर में लागू करने की आवश्यकता होती है और संचार को व्यवस्थित करने के लिए क्लाइंट पर उपयोग किए जाने वाले तरीके। यह काफी सुविधाजनक रूप से निकलता है, हम अपनी सेवाओं का एक ही प्रारूप में वर्णन कर सकते हैं और प्रोग्रामिंग भाषा के लिए कोड तैयार कर सकते हैं जिसमें प्रत्येक इंटरैक्शन तत्वों का वर्णन किया जाएगा।

साथ ही, फ्रेमवर्क के विकास में कोड पीढ़ी का उपयोग किया जा सकता है। उदाहरण के लिए, कोड को लागू करने के लिए जिसे एप्लिकेशन डेवलपर द्वारा लिखा जाना आवश्यक नहीं है, लेकिन सही संचालन के लिए यह आवश्यक है। उदाहरण के लिए, प्रपत्र फ़ील्ड सत्यापनकर्ता, मिडिलवेयर स्वचालित पीढ़ी, डीबीएमएस के लिए ग्राहकों की गतिशील पीढ़ी बनाने के लिए।

जाओ कोड जनरेटर कार्यान्वयन


आइए हम अभ्यास में जांचते हैं कि गो में कोड जनरेशन का तंत्र कैसे काम करता है। सबसे पहले, एएसटी - सार सिंटैक्स ट्री या सार सिंटैक्स ट्री का उल्लेख करना आवश्यक है। विवरण के लिए, आप विकिपीडिया पर जा सकते हैं । हमारे उद्देश्यों के लिए, यह समझना आवश्यक है कि पूरा कार्यक्रम एक ग्राफ के रूप में बनाया गया है, जहां वर्टिकल प्रोग्रामिंग भाषा के संचालकों के साथ मैप किए गए (चिह्नित) हैं, और संबंधित ऑपरेंड के साथ निकल जाते हैं।

तो, शुरुआत के लिए, हमें पैकेज चाहिए:

go / ast
go / parser /
go / token /

फ़ाइल को कोड के साथ पार्स करना और पेड़ को संकलित करना निम्न कमांड द्वारा किया जाता है


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

हम संकेत देते हैं कि फ़ाइल नाम कमांड लाइन के पहले तर्क से लिया जाना चाहिए, हम यह भी पूछते हैं कि टिप्पणियों को पेड़ में जोड़ा जाए।

सामान्य तौर पर, कोड पीढ़ी को नियंत्रित करने के लिए, उपयोगकर्ता (कोड के विकासकर्ता जिसके आधार पर अन्य कोड उत्पन्न होता है) टिप्पणियों या टैग का उपयोग कर सकता है (जैसा कि हम संरचना के पास `json:" "` लिखते हैं)।

एक उदाहरण के लिए, हम एक डेटाबेस के साथ काम करने के लिए एक कोड जनरेटर लिखेंगे। कोड जनरेटर इसे स्थानांतरित की गई फ़ाइल को देखेगा, उन संरचनाओं की तलाश करेगा जिनके पास एक समान टिप्पणी है और डेटाबेस इंटरैक्शन के लिए संरचना (CRUD विधियों) पर एक आवरण बनाएं। हम मापदंडों का उपयोग करेंगे:

  • dbe टिप्पणी: {"तालिका": "उपयोगकर्ता"}, जिसमें आप उस तालिका को परिभाषित कर सकते हैं जिसमें संरचना रिकॉर्ड होगी;
  • संरचना के क्षेत्रों के लिए dbe टैग, जिसमें आप उस कॉलम का नाम निर्दिष्ट कर सकते हैं जिसमें फ़ील्ड मान और विशेषताओं को डेटाबेस के लिए रखना है: Primary_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
}


अब हम संरचना के क्षेत्रों के बारे में जानकारी तैयार करेंगे, ताकि प्राप्त जानकारी के आधार पर, हम तालिका निर्माण कार्य (क्रिएटटेबल) और सीआरयूडी के तरीके उत्पन्न करेंगे।

एक संरचना से फ़ील्ड प्राप्त करने के लिए कोड

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 फ़ील्ड प्रकार में कम कर देंगे, हालांकि सामान्य तौर पर, आप स्तंभ प्रकार की परिभाषा को टैग में जोड़ सकते हैं ताकि आप अधिक सूक्ष्मता से कॉन्फ़िगर कर सकें। दूसरी ओर,कोई भी पहले से डेटाबेस में वांछित तालिका बनाने के लिए, या स्वचालित रूप से सही करने के लिए परेशान नहीं करता है।

संरचना को पार्स करने के बाद, हम टेबल क्रिएशन फंक्शन के लिए कोड बनाने की विधि और क्रिएट, क्वेरी, अपडेट, डिलीट फंक्शन बनाने के तरीके शुरू करते हैं। हम प्रत्येक फ़ंक्शन के लिए एक एसक्यूएल अभिव्यक्ति और चलाने के लिए एक बंधन तैयार करते हैं। मुझे त्रुटि से परेशान नहीं किया गया है, मैं सिर्फ डेटाबेस ड्राइवर से त्रुटि देता हूं। कोड जनरेशन के लिए, टेम्प्लेट / टेम्प्लेट लाइब्रेरी से टेम्प्लेट का उपयोग करना सुविधाजनक है। उनकी मदद से, आप बहुत अधिक समर्थित और पूर्वानुमानित कोड प्राप्त कर सकते हैं (कोड तुरंत दिखाई देता है, लेकिन जनरेटर कोड द्वारा स्मियर नहीं किया जाता है)।

टेबल निर्माण

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 का समर्थन किया जाता है;
  • सभी फ़ील्ड प्रकार समर्थित नहीं हैं।
  • चयन के लिए कोई फ़िल्टरिंग और सीमाएँ नहीं हैं।

हालाँकि, बग्स को ठीक करना गो सोर्स कोड को पार्स करने और इसके आधार पर नए कोड उत्पन्न करने के दायरे से परे है।

इस तरह के परिदृश्य में कोड जनरेटर का उपयोग करने से आप केवल एक ही स्थान पर उपयोग किए जाने वाले फ़ील्ड और प्रकार की संरचनाओं को बदल सकते हैं, डेटाबेस के साथ सहभागिता के लिए कोड में परिवर्तन करने के लिए याद रखने की आवश्यकता नहीं है, आपको बस हर बार कोड जनरेटर चलाने की आवश्यकता है। इस कार्य को परावर्तन की सहायता से हल किया जा सकता था, लेकिन इससे प्रदर्शन प्रभावित होता।

स्रोत कोड जनरेटर और जेनथब पर पोस्ट किए गए उत्पन्न कोड का एक उदाहरण

All Articles