diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml new file mode 100644 index 00000000..6865261b --- /dev/null +++ b/.github/workflows/gh-pages.yaml @@ -0,0 +1,36 @@ +name: github pages + +on: + push: + branches: + - gh-pages # Set a branch that will trigger a deployment + pull_request: + branches: + - gh-pages + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + ref: gh-pages + submodules: true # Fetch Hugo themes (true OR recursive) + fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: '0.79.1' + # extended: true + + - name: Build + run: cd src && hugo --minify && cd .. + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/gh-pages' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: . \ No newline at end of file diff --git a/.github/workflows/go-sql-tests.yaml b/.github/workflows/go-sql-tests.yaml index 5f081e3a..d1b4c9cf 100644 --- a/.github/workflows/go-sql-tests.yaml +++ b/.github/workflows/go-sql-tests.yaml @@ -21,11 +21,35 @@ jobs: with: go-version: 1.19 - - name: Build + - name: Build core + run: go build -v ./... + working-directory: ./go/core + + - name: Test core + run: go test -v ./... + working-directory: ./go/core + + - name: Build net/http + run: go build -v ./... + working-directory: ./go/net/http + + - name: Test net/http + run: go test -v ./... + working-directory: ./go/net/http + + - name: Build gorilla/mux + run: go build -v ./... + working-directory: ./go/gorrila/mux + + - name: Test gorilla/mux + run: go test -v ./... + working-directory: ./go/gorrila/mux + + - name: Build database/sql run: go build -v ./... working-directory: ./go/database/sql - - name: Test go-sql + - name: Test database/sql run: go test -v ./... working-directory: ./go/database/sql @@ -42,4 +66,3 @@ jobs: # Verify go fmt standards are used - name: Format run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi - diff --git a/go/core/core.go b/go/core/core.go index 6fca682a..beb7207f 100644 --- a/go/core/core.go +++ b/go/core/core.go @@ -26,16 +26,21 @@ import ( "go.opentelemetry.io/otel/propagation" ) +// Constants used as key string for tags. +// It is not necessary that all SQLCommenter frameworks/ORMs will contain all these keys i.e. +// it is on best-effort basis. const ( Route string = "route" - Controller string = "controller" - Action string = "action" - Framework string = "framework" - Driver string = "db_driver" - Traceparent string = "traceparent" - Application string = "application" + Controller = "controller" + Action = "action" + Framework = "framework" + Driver = "db_driver" + Traceparent = "traceparent" + Application = "application" ) +// CommenterConfig contains configurations for SQLCommenter library. +// We can enable and disable certain tags by enabling these configurations. type CommenterConfig struct { EnableDBDriver bool EnableRoute bool @@ -46,32 +51,42 @@ type CommenterConfig struct { EnableApplication bool } +// StaticTags are few tags that can be set by the application and will be constant +// for every API call. type StaticTags struct { Application string DriverName string } +// CommenterOptions contains all options regarding SQLCommenter library. +// This includes the configurations as well as any static tags. type CommenterOptions struct { Config CommenterConfig Tags StaticTags } func encodeURL(k string) string { - return url.QueryEscape(string(k)) + return url.QueryEscape(k) } -func GetFunctionName(i interface{}) string { +// GetFunctionName returns the name of the function passed. +func GetFunctionName(i any) string { if i == nil { return "" } return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() } +// ConvertMapToComment returns a comment string given a map of key-value pairs of tags. +// There are few steps involved here: +// - Sorting the tags by key string +// - url encoding the key value pairs +// - Formatting the key value pairs as "key1=value1,key2=value2" format. func ConvertMapToComment(tags map[string]string) string { var sb strings.Builder i, sz := 0, len(tags) - //sort by keys + // sort by keys sortedKeys := make([]string, 0, len(tags)) for k := range tags { sortedKeys = append(sortedKeys, k) @@ -80,15 +95,16 @@ func ConvertMapToComment(tags map[string]string) string { for _, key := range sortedKeys { if i == sz-1 { - sb.WriteString(fmt.Sprintf("%s=%v", encodeURL(key), encodeURL(tags[key]))) + sb.WriteString(fmt.Sprintf("%s='%s'", encodeURL(key), encodeURL(tags[key]))) } else { - sb.WriteString(fmt.Sprintf("%s=%v,", encodeURL(key), encodeURL(tags[key]))) + sb.WriteString(fmt.Sprintf("%s='%s',", encodeURL(key), encodeURL(tags[key]))) } i++ } return sb.String() } +// ExtractTraceparent extracts the traceparent field using OpenTelemetry library. func ExtractTraceparent(ctx context.Context) propagation.MapCarrier { // Serialize the context into carrier textMapPropogator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) @@ -97,12 +113,15 @@ func ExtractTraceparent(ctx context.Context) propagation.MapCarrier { return carrier } +// RequestTagsProvider adds a basic interface for other libraries like gorilla/mux to implement. type RequestTagsProvider interface { Route() string Action() string Framework() string } +// ContextInject injects the tags key-value pairs into context, +// which can be later passed into drivers/ORMs to finally inject them into SQL queries. func ContextInject(ctx context.Context, h RequestTagsProvider) context.Context { ctx = context.WithValue(ctx, Route, h.Route()) ctx = context.WithValue(ctx, Action, h.Action()) diff --git a/go/core/core_test.go b/go/core/core_test.go new file mode 100644 index 00000000..fb33c515 --- /dev/null +++ b/go/core/core_test.go @@ -0,0 +1,101 @@ +package core + +import ( + "context" + "testing" +) + +func TestConvertMapToComment(t *testing.T) { + for _, tc := range []struct { + desc string + tagMap map[string]string + want string + }{ + { + desc: "nil tagMap", + want: "", + }, + { + desc: "no tags", + tagMap: map[string]string{}, + want: "", + }, + { + desc: "only one tag", + tagMap: map[string]string{ + Route: "test-route", + }, + want: "route='test-route'", + }, + { + desc: "only one tag with url encoding", + tagMap: map[string]string{ + Route: "test/route", + }, + want: "route='test%2Froute'", + }, + { + desc: "multiple tags", + tagMap: map[string]string{ + Route: "test/route", + Action: "test-action", + Driver: "sql-pg", + }, + want: "action='test-action',db_driver='sql-pg',route='test%2Froute'", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + if got, want := ConvertMapToComment(tc.tagMap), tc.want; got != want { + t.Errorf("ConvertMapToComment(%+v) = %q, want = %q", tc.tagMap, got, want) + } + }) + } +} + +type testRequestProvider struct { + withRoute string + withAction string + withFramework string +} + +func (p *testRequestProvider) Route() string { return p.withRoute } +func (p *testRequestProvider) Action() string { return p.withAction } +func (p *testRequestProvider) Framework() string { return p.withFramework } + +func TestContextInject(t *testing.T) { + tagsProvider := &testRequestProvider{ + withRoute: "test-route", + withAction: "test-action", + withFramework: "test-framework", + } + ctx := context.Background() + gotCtx := ContextInject(ctx, tagsProvider) + + for _, tc := range []struct { + desc string + key string + want string + }{ + { + desc: "fetch action", + key: Action, + want: "test-action", + }, + { + desc: "fetch route", + key: Route, + want: "test-route", + }, + { + desc: "fetch framework", + key: Framework, + want: "test-framework", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + if got, want := gotCtx.Value(tc.key), tc.want; got != want { + t.Errorf("ContextInject(ctx, tagsProvider) context.Value(%q) = %q, want = %q", tc.key, got, tc.want) + } + }) + } +} diff --git a/go/database/sql/connection_test.go b/go/database/sql/connection_test.go index f5ed0f82..1fcdc9f5 100644 --- a/go/database/sql/connection_test.go +++ b/go/database/sql/connection_test.go @@ -41,7 +41,7 @@ func TestWithComment_NoContext(t *testing.T) { Config: core.CommenterConfig{EnableDBDriver: true}, }, query: "SELECT 1;", - wantQuery: "SELECT 1/*db_driver=database%2Fsql%3A*/;", + wantQuery: "SELECT 1/*db_driver='database%2Fsql%3A'*/;", }, { desc: "enable DBDriver and pass static tag driver name", @@ -50,7 +50,7 @@ func TestWithComment_NoContext(t *testing.T) { Tags: core.StaticTags{DriverName: "postgres"}, }, query: "SELECT 1;", - wantQuery: "SELECT 1/*db_driver=database%2Fsql%3Apostgres*/;", + wantQuery: "SELECT 1/*db_driver='database%2Fsql%3Apostgres'*/;", }, { desc: "enable DBDriver and pass all static tags", @@ -59,7 +59,7 @@ func TestWithComment_NoContext(t *testing.T) { Tags: core.StaticTags{DriverName: "postgres", Application: "app-1"}, }, query: "SELECT 1;", - wantQuery: "SELECT 1/*db_driver=database%2Fsql%3Apostgres*/;", + wantQuery: "SELECT 1/*db_driver='database%2Fsql%3Apostgres'*/;", }, { desc: "enable other tags and pass all static tags", @@ -68,7 +68,7 @@ func TestWithComment_NoContext(t *testing.T) { Tags: core.StaticTags{DriverName: "postgres", Application: "app-1"}, }, query: "SELECT 1;", - wantQuery: "SELECT 1/*application=app-1,db_driver=database%2Fsql%3Apostgres*/;", + wantQuery: "SELECT 1/*application='app-1',db_driver='database%2Fsql%3Apostgres'*/;", }, } for _, tc := range testCases { @@ -122,7 +122,7 @@ func TestWithComment_WithContext(t *testing.T) { }, ), query: "SELECT 1;", - wantQuery: "SELECT 1/*application=app-1,db_driver=database%2Fsql%3Apostgres,framework=custom-golang,route=listData*/;", + wantQuery: "SELECT 1/*application='app-1',db_driver='database%2Fsql%3Apostgres',framework='custom-golang',route='listData'*/;", }, { desc: "only all options but context contains all tags", @@ -147,7 +147,7 @@ func TestWithComment_WithContext(t *testing.T) { }, ), query: "SELECT 1;", - wantQuery: "SELECT 1/*action=any+action,application=app-1,db_driver=database%2Fsql%3Apostgres,framework=custom-golang,route=listData*/;", + wantQuery: "SELECT 1/*action='any+action',application='app-1',db_driver='database%2Fsql%3Apostgres',framework='custom-golang',route='listData'*/;", }, } for _, tc := range testCases { diff --git a/go/database/sql/go.mod b/go/database/sql/go.mod index b0edf01e..59afde4e 100644 --- a/go/database/sql/go.mod +++ b/go/database/sql/go.mod @@ -2,7 +2,7 @@ module github.com/google/sqlcommenter/go/database/sql go 1.19 -require github.com/google/sqlcommenter/go/core v0.0.5-beta +require github.com/google/sqlcommenter/go/core v0.1.2 require go.opentelemetry.io/otel v1.11.1 // indirect diff --git a/go/database/sql/go.sum b/go/database/sql/go.sum index 2342027e..b2f0d072 100644 --- a/go/database/sql/go.sum +++ b/go/database/sql/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/sqlcommenter/go/core v0.0.5-beta h1:axqYR1zQCCdRBLnwr/j+ckllBSBJ7uaVdsnANuGzCUI= -github.com/google/sqlcommenter/go/core v0.0.5-beta/go.mod h1:GORu2htXRC4xtejBzOa4ct1L20pohP81DFNYKdCJI70= +github.com/google/sqlcommenter/go/core v0.1.2 h1:UM3jS7JROrPTsJxbLq68PRB34Iq8H3AZQDQUSV7sQWU= +github.com/google/sqlcommenter/go/core v0.1.2/go.mod h1:GORu2htXRC4xtejBzOa4ct1L20pohP81DFNYKdCJI70= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= diff --git a/go/gorrila/mux/go.mod b/go/gorrila/mux/go.mod index b0129dcb..354f7dce 100644 --- a/go/gorrila/mux/go.mod +++ b/go/gorrila/mux/go.mod @@ -3,7 +3,7 @@ module github.com/google/sqlcommenter/go/gorrila/mux go 1.19 require ( - github.com/google/sqlcommenter/go/core v0.0.5-beta + github.com/google/sqlcommenter/go/core v0.1.0 github.com/google/sqlcommenter/go/net/http v0.0.3-beta github.com/gorilla/mux v1.8.0 ) diff --git a/go/gorrila/mux/go.sum b/go/gorrila/mux/go.sum index c6f852c7..93704cb1 100644 --- a/go/gorrila/mux/go.sum +++ b/go/gorrila/mux/go.sum @@ -4,6 +4,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/sqlcommenter/go/core v0.0.5-beta h1:axqYR1zQCCdRBLnwr/j+ckllBSBJ7uaVdsnANuGzCUI= github.com/google/sqlcommenter/go/core v0.0.5-beta/go.mod h1:GORu2htXRC4xtejBzOa4ct1L20pohP81DFNYKdCJI70= +github.com/google/sqlcommenter/go/core v0.1.0 h1:g5jL8HUk2Ko9mMPoCI4jP47dyJhpNyu6E8WmiEhh8OU= +github.com/google/sqlcommenter/go/core v0.1.0/go.mod h1:GORu2htXRC4xtejBzOa4ct1L20pohP81DFNYKdCJI70= github.com/google/sqlcommenter/go/net/http v0.0.3-beta h1:IE/vO3xKddn/2Bq3k+hSy4CxcEuvE1lUiIDYTXjApzA= github.com/google/sqlcommenter/go/net/http v0.0.3-beta/go.mod h1:duXQQvXZYCX8eQ+XOrlojWF512ltEp1eSKXc/KiS9lg= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= diff --git a/ruby/sqlcommenter-ruby/README.md b/ruby/sqlcommenter-ruby/README.md index 26380a40..2ca1b82f 100644 --- a/ruby/sqlcommenter-ruby/README.md +++ b/ruby/sqlcommenter-ruby/README.md @@ -1 +1,19 @@ -# sqlcommenter-ruby \ No newline at end of file +# SQLCommenter in Ruby + +## SQLCommenter support in Rails + + +Support for [SQLCommenter](https://google.github.io/sqlcommenter/) in [Ruby on Rails](https://rubyonrails.org/) varies, depending on your versions of Rails. + +If you are on Rails version: + + - **7.1 and above:** SQLCommenter is supported by default. Enable [query_log_tags](https://guides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-enabled), and SQLCommenter formatting will be [enabled by default](https://edgeguides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-format). + - **7.0:** Enable [query_log_tags](https://guides.rubyonrails.org/configuring.html#config-active-record-query-log-tags-enabled) and install the [PlanetScale SQLCommenter gem](https://github.com/planetscale/activerecord-sql_commenter#installation) for SQLCommenter support. + - **Below 7.0:** Refer to the [sqlcommenter_rails gem](https://github.com/google/sqlcommenter/tree/master/ruby/sqlcommenter-ruby/sqlcommenter_rails) in this directory for adding SQLCommenter support. Note that this requires additional work because you will have to install a fork of the [marginalia](https://github.com/basecamp/marginalia/) gem, which has since been consolidated into Rails 7.0 and up. + +## Tracing support in Rails + +Tracing support has been implemented in the [marginalia-opencensus gem]: https://github.com/google/sqlcommenter/tree/master/ruby/sqlcommenter-ruby/marginalia-opencensus. Note that this only works for Rails versions below 7.0, before the [marginalia](https://github.com/basecamp/marginalia/) gem was consolidated into Rails. + +Re-purposing that gem for Rails versions >=7.0 should only require minor modifications (contributions are welcome!). + diff --git a/ruby/sqlcommenter-ruby/sqlcommenter_rails/README.md b/ruby/sqlcommenter-ruby/sqlcommenter_rails/README.md index dc30dd3d..f8f57ef8 100644 --- a/ruby/sqlcommenter-ruby/sqlcommenter_rails/README.md +++ b/ruby/sqlcommenter-ruby/sqlcommenter_rails/README.md @@ -1,5 +1,7 @@ # sqlcommenter_rails +**If you are on Rails version 7.0 and up, refer to the [sqlcommenter-ruby README] instead** + [sqlcommenter] for [Ruby on Rails]. Powered by [marginalia] and [marginalia-opencensus]. @@ -8,6 +10,7 @@ Powered by [marginalia] and [marginalia-opencensus]. [Ruby on Rails]: https://rubyonrails.org/ [marginalia]: https://github.com/basecamp/marginalia/ [marginalia-opencensus]: https://github.com/google/sqlcommenter/tree/master/ruby/sqlcommenter-ruby/marginalia-opencensus +[sqlcommenter-ruby README]: https://github.com/google/sqlcommenter/tree/master/ruby/sqlcommenter-ruby ## Installation diff --git a/ruby/sqlcommenter-ruby/sqlcommenter_rails_demo/README.md b/ruby/sqlcommenter-ruby/sqlcommenter_rails_demo/README.md index cdb5e2aa..9c7491ec 100644 --- a/ruby/sqlcommenter-ruby/sqlcommenter_rails_demo/README.md +++ b/ruby/sqlcommenter-ruby/sqlcommenter_rails_demo/README.md @@ -1,9 +1,12 @@ # sqlcommenter_rails demo +**If you are on Rails version 7.0 and up, refer to the [sqlcommenter-ruby README] instead** + This is a demo [Rails API] application to demonstrate [sqlcommenter_rails] integration. [Rails API]: https://guides.rubyonrails.org/api_app.html [sqlcommenter_rails]: https://github.com/google/sqlcommenter/ruby/sqlcommenter-ruby/sqlcommenter_rails +[sqlcommenter-ruby README]: https://github.com/google/sqlcommenter/tree/master/ruby/sqlcommenter-ruby ## Setup