package opts import ( "encoding/json" "fmt" "maps" "path/filepath" "github.com/sqlc-dev/sqlc/internal/plugin" ) type Options struct { EmitInterface bool `json:"emit_interface" yaml:"emit_interface"` EmitJsonTags bool `json:"emit_json_tags" yaml:"emit_json_tags"` JsonTagsIdUppercase bool `json:"json_tags_id_uppercase" yaml:"json_tags_id_uppercase"` EmitDbTags bool `json:"emit_db_tags" yaml:"emit_db_tags"` EmitPreparedQueries bool `json:"emit_prepared_queries" yaml:"emit_prepared_queries"` EmitExactTableNames bool `json:"emit_exact_table_names,omitempty" yaml:"emit_exact_table_names"` EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"` EmitExportedQueries bool `json:"emit_exported_queries" yaml:"emit_exported_queries"` EmitResultStructPointers bool `json:"emit_result_struct_pointers" yaml:"emit_result_struct_pointers"` EmitParamsStructPointers bool `json:"emit_params_struct_pointers" yaml:"emit_params_struct_pointers"` EmitMethodsWithDbArgument bool `json:"emit_methods_with_db_argument,omitempty" yaml:"emit_methods_with_db_argument"` EmitPointersForNullTypes bool `json:"emit_pointers_for_null_types" yaml:"emit_pointers_for_null_types"` // nil inherits EmitPointersForNullTypes; non-nil overrides for enums only. EmitPointersForNullEnumTypes *bool `json:"emit_pointers_for_null_enum_types,omitempty" yaml:"emit_pointers_for_null_enum_types"` EmitEnumValidMethod bool `json:"emit_enum_valid_method,omitempty" yaml:"emit_enum_valid_method"` EmitAllEnumValues bool `json:"emit_all_enum_values,omitempty" yaml:"emit_all_enum_values"` EmitSqlAsComment bool `json:"emit_sql_as_comment,omitempty" yaml:"emit_sql_as_comment"` JsonTagsCaseStyle string `json:"json_tags_case_style,omitempty" yaml:"json_tags_case_style"` Package string `json:"package" yaml:"package"` Out string `json:"out" yaml:"out"` Overrides []Override `json:"overrides,omitempty" yaml:"overrides"` Rename map[string]string `json:"rename,omitempty" yaml:"rename"` SqlPackage string `json:"sql_package" yaml:"sql_package"` SqlDriver string `json:"sql_driver" yaml:"sql_driver"` OutputBatchFileName string `json:"output_batch_file_name,omitempty" yaml:"output_batch_file_name"` OutputDbFileName string `json:"output_db_file_name,omitempty" yaml:"output_db_file_name"` OutputModelsFileName string `json:"output_models_file_name,omitempty" yaml:"output_models_file_name"` OutputModelsPath string `json:"output_models_path,omitempty" yaml:"output_models_path"` OutputModelsPackage string `json:"output_models_package,omitempty" yaml:"output_models_package"` OutputModelsImport string `json:"output_models_import,omitempty" yaml:"output_models_import"` OutputModelsEmit *bool `json:"output_models_emit,omitempty" yaml:"output_models_emit"` OutputQuerierFileName string `json:"output_querier_file_name,omitempty" yaml:"output_querier_file_name"` OutputCopyfromFileName string `json:"output_copyfrom_file_name,omitempty" yaml:"output_copyfrom_file_name"` OutputFilesSuffix string `json:"output_files_suffix,omitempty" yaml:"output_files_suffix"` InflectionExcludeTableNames []string `json:"inflection_exclude_table_names,omitempty" yaml:"inflection_exclude_table_names"` WrapErrors bool `json:"wrap_errors,omitempty" yaml:"wrap_errors"` QueryParameterLimit *int32 `json:"query_parameter_limit,omitempty" yaml:"query_parameter_limit"` OmitSqlcVersion bool `json:"omit_sqlc_version,omitempty" yaml:"omit_sqlc_version"` OmitUnusedStructs bool `json:"omit_unused_structs,omitempty" yaml:"omit_unused_structs"` BuildTags string `json:"build_tags,omitempty" yaml:"build_tags"` Initialisms *[]string `json:"initialisms,omitempty" yaml:"initialisms"` InitialismsMap map[string]struct{} `json:"-" yaml:"-"` } type GlobalOptions struct { Overrides []Override `json:"overrides,omitempty" yaml:"overrides"` Rename map[string]string `json:"rename,omitempty" yaml:"rename"` } func Parse(req *plugin.GenerateRequest) (*Options, error) { options, err := parseOpts(req) if err != nil { return nil, err } global, err := parseGlobalOpts(req) if err != nil { return nil, err } if len(global.Overrides) > 0 { options.Overrides = append(global.Overrides, options.Overrides...) } if len(global.Rename) > 0 { if options.Rename == nil { options.Rename = map[string]string{} } maps.Copy(options.Rename, global.Rename) } return options, nil } func parseOpts(req *plugin.GenerateRequest) (*Options, error) { var options Options if len(req.PluginOptions) == 0 { return &options, nil } if err := json.Unmarshal(req.PluginOptions, &options); err != nil { return nil, fmt.Errorf("unmarshalling plugin options: %w", err) } if options.Package == "" { if options.Out != "" { options.Package = filepath.Base(options.Out) } else { return nil, fmt.Errorf("invalid options: missing package name") } } // Default the models package name to the base of the models path. When // the user only configures output_models_emit: false (no path), fall // back to the base of the import path. if options.OutputModelsPackage == "" { if options.OutputModelsPath != "" { options.OutputModelsPackage = filepath.Base(options.OutputModelsPath) } else if options.OutputModelsImport != "" { options.OutputModelsPackage = filepath.Base(options.OutputModelsImport) } } for i := range options.Overrides { if err := options.Overrides[i].parse(req); err != nil { return nil, err } } if options.SqlPackage != "" { if err := validatePackage(options.SqlPackage); err != nil { return nil, fmt.Errorf("invalid options: %s", err) } } if options.SqlDriver != "" { if err := validateDriver(options.SqlDriver); err != nil { return nil, fmt.Errorf("invalid options: %s", err) } } if options.QueryParameterLimit == nil { options.QueryParameterLimit = new(int32) *options.QueryParameterLimit = 1 } if options.Initialisms == nil { options.Initialisms = new([]string) *options.Initialisms = []string{"id"} } options.InitialismsMap = map[string]struct{}{} for _, initial := range *options.Initialisms { options.InitialismsMap[initial] = struct{}{} } return &options, nil } func parseGlobalOpts(req *plugin.GenerateRequest) (*GlobalOptions, error) { var options GlobalOptions if len(req.GlobalOptions) == 0 { return &options, nil } if err := json.Unmarshal(req.GlobalOptions, &options); err != nil { return nil, fmt.Errorf("unmarshalling global options: %w", err) } for i := range options.Overrides { if err := options.Overrides[i].parse(req); err != nil { return nil, err } } return &options, nil } func ValidateOpts(opts *Options) error { if opts.EmitMethodsWithDbArgument && opts.EmitPreparedQueries { return fmt.Errorf("invalid options: emit_methods_with_db_argument and emit_prepared_queries options are mutually exclusive") } if *opts.QueryParameterLimit < 0 { return fmt.Errorf("invalid options: query parameter limit must not be negative") } if err := validateModelsOptions(opts); err != nil { return err } return nil } // ModelsEmitEnabled reports whether this codegen block should write the // models file. Defaults to true when the option is unset. func (o *Options) ModelsEmitEnabled() bool { if o.OutputModelsEmit == nil { return true } return *o.OutputModelsEmit } // ModelsImportAlias is the fixed Go import alias used for the models // package in query files. Using a constant alias keeps the type qualifier // consistent regardless of how the user names the actual package. const ModelsImportAlias = "models" // ModelsPackage returns the Go package name to use in the models file // itself (i.e. the `package X` declaration). When the caller has not // configured a separate models package, this is the same as Package. func (o *Options) ModelsPackage() string { if o.OutputModelsPackage != "" { return o.OutputModelsPackage } return o.Package } // ModelsAreExternal reports whether model types live in a different Go // package than the queries package. When true, query files must import the // models package and reference types as `models.Type`. func (o *Options) ModelsAreExternal() bool { return o.OutputModelsImport != "" } // ModelsTypeQualifier returns the prefix to use when referencing a model // type from a query file ("models."). Empty string when no qualifier is // needed. func (o *Options) ModelsTypeQualifier() string { if o.ModelsAreExternal() { return ModelsImportAlias + "." } return "" } func validateModelsOptions(opts *Options) error { hasAnyModelsOpt := opts.OutputModelsPath != "" || opts.OutputModelsPackage != "" || opts.OutputModelsImport != "" || opts.OutputModelsEmit != nil if !hasAnyModelsOpt { return nil } if opts.OutputModelsImport == "" { return fmt.Errorf("invalid options: output_models_import is required when any output_models_* option is set") } if opts.ModelsEmitEnabled() && opts.OutputModelsPath == "" { return fmt.Errorf("invalid options: output_models_path is required when emitting models to a separate package") } if opts.ModelsEmitEnabled() && opts.OutputModelsPath == opts.Out { return fmt.Errorf("invalid options: output_models_path matches out; models would overwrite the queries package") } return nil }