Ejemplo n.º 1
0
    def delete_model(self, model):
        """
        Drop the model's table in the database along with any unique constraints
        or indexes it has.

        :type model: :class:`~django.db.migrations.operations.models.ModelOperation`
        :param model: A model for creating a table.
        """
        # Spanner requires dropping all of a table's indexes before dropping
        # the table.
        index_names = self._constraint_names(model,
                                             index=True,
                                             primary_key=False)
        for index_name in index_names:
            trace_attributes = {
                "model_name": self.quote_name(model._meta.db_table),
                "index_name": index_name,
            }
            with trace_call(
                    "CloudSpannerDjango.delete_model.delete_index",
                    self.connection,
                    trace_attributes,
            ):
                self.execute(self._delete_index_sql(model, index_name))
        trace_attributes = {
            "model_name": self.quote_name(model._meta.db_table)
        }
        with trace_call(
                "CloudSpannerDjango.delete_model",
                self.connection,
                trace_attributes,
        ):
            super().delete_model(model)
Ejemplo n.º 2
0
        def test_trace_call(self):
            extra_attributes = {
                "attribute1": "value1",
                # Since our database is mocked, we have to override the db.instance parameter so it is a string
                "db.instance": "database_name",
            }

            expected_attributes = {
                "db.type": "spanner",
                "db.engine": "django_spanner",
                "db.project": PROJECT,
                "db.instance": INSTANCE_ID,
                "db.name": DATABASE_ID,
            }
            expected_attributes.update(extra_attributes)

            with _opentelemetry_tracing.trace_call("CloudSpannerDjango.Test",
                                                   _make_connection(),
                                                   extra_attributes) as span:
                span.set_attribute("after_setup_attribute", 1)

            expected_attributes["after_setup_attribute"] = 1

            span_list = self.ot_exporter.get_finished_spans()
            self.assertEqual(len(span_list), 1)
            span = span_list[0]
            self.assertEqual(span.kind, trace_api.SpanKind.CLIENT)
            self.assertEqual(span.attributes, expected_attributes)
            self.assertEqual(span.name, "CloudSpannerDjango.Test")
            self.assertEqual(span.status.status_code, StatusCode.OK)
Ejemplo n.º 3
0
        def test_trace_error(self):
            extra_attributes = {"db.instance": "database_name"}

            expected_attributes = {
                "db.type": "spanner",
                "db.engine": "django_spanner",
                "db.project": os.environ["GOOGLE_CLOUD_PROJECT"],
                "db.instance": "instance_id",
                "db.name": "database_id",
            }
            expected_attributes.update(extra_attributes)

            with self.assertRaises(GoogleAPICallError):
                with _opentelemetry_tracing.trace_call(
                        "CloudSpannerDjango.Test",
                        _make_connection(),
                        extra_attributes,
                ) as span:
                    from google.api_core.exceptions import InvalidArgument

                    raise _make_rpc_error(InvalidArgument)

            span_list = self.ot_exporter.get_finished_spans()
            self.assertEqual(len(span_list), 1)
            span = span_list[0]
            self.assertEqual(span.kind, trace_api.SpanKind.CLIENT)
            self.assertEqual(dict(span.attributes), expected_attributes)
            self.assertEqual(span.name, "CloudSpannerDjango.Test")
            self.assertEqual(span.status.status_code, StatusCode.ERROR)
Ejemplo n.º 4
0
    def add_index(self, model, index):
        """Add index to model's table.

        :type model: :class:`~django.db.migrations.operations.models.ModelOperation`
        :param model: A model for creating a table.

        :type index: :class:`~django.db.migrations.operations.models.Index`
        :param index: An index to add.
        """
        # Work around a bug in Django where a space isn't inserting before
        # DESC: https://code.djangoproject.com/ticket/30961
        # This method can be removed in Django 3.1.
        index.fields_orders = [
            (field_name, " DESC" if order == "DESC" else "")
            for field_name, order in index.fields_orders
        ]
        trace_attributes = {
            "model_name": self.quote_name(model._meta.db_table),
            "index": "|".join(index.fields),
        }
        with trace_call(
            "CloudSpannerDjango.add_index",
            self.connection,
            trace_attributes,
        ):
            super().add_index(model, index)
Ejemplo n.º 5
0
    def remove_field(self, model, field):
        """
        Remove the column(s) representing the field from the model's table,
        along with any unique constraints, foreign key constraints, or indexes
        caused by that field. If the field is a ManyToManyField without a
        value for through, it will remove the table created to track the
        relationship. If through is provided, it is a no-op.

        :type model: :class:`~django.db.migrations.operations.models.ModelOperation`
        :param model: A model for creating a table.

        :type field: :class:`~django.db.migrations.operations.models.fields.FieldOperation`
        :param field: The field of the table.
        """
        # Spanner requires dropping a column's indexes before dropping the
        # column.
        index_names = self._constraint_names(model, [field.column], index=True)
        for index_name in index_names:
            trace_attributes = {
                "model_name": self.quote_name(model._meta.db_table),
                "field": field.column,
                "index_name": index_name,
            }
            with trace_call(
                    "CloudSpannerDjango.remove_field.delete_index",
                    self.connection,
                    trace_attributes,
            ):
                self.execute(self._delete_index_sql(model, index_name))

        trace_attributes = {
            "model_name": self.quote_name(model._meta.db_table),
            "field": field.column,
        }
        with trace_call(
                "CloudSpannerDjango.remove_field",
                self.connection,
                trace_attributes,
        ):
            super().remove_field(model, field)
Ejemplo n.º 6
0
    def create_model(self, model):
        """
        Create a table and any accompanying indexes or unique constraints for
        the given `model`.

        :type model: :class:`~django.db.migrations.operations.models.ModelOperation`
        :param model: A model for creating a table.
        """
        # Create column SQL, add FK deferreds if needed
        column_sqls = []
        params = []
        for field in model._meta.local_fields:
            # SQL
            definition, extra_params = self.column_sql(model, field)
            if definition is None:
                continue
            # Check constraints can go on the column SQL here
            db_params = field.db_parameters(connection=self.connection)
            if db_params["check"]:
                definition += (", CONSTRAINT constraint_%s_%s_%s " % (
                    model._meta.db_table,
                    self.quote_name(field.name),
                    uuid.uuid4().hex[:6].lower(),
                ) + self.sql_check_constraint % db_params)
            # Autoincrement SQL (for backends with inline variant)
            col_type_suffix = field.db_type_suffix(connection=self.connection)
            if col_type_suffix:
                definition += " %s" % col_type_suffix
            params.extend(extra_params)
            # FK
            if field.remote_field and field.db_constraint:
                from_table = field.model._meta.db_table
                from_column = field.column
                to_table = field.remote_field.model._meta.db_table
                to_column = field.remote_field.model._meta.get_field(
                    field.remote_field.field_name).column
                if self.sql_create_inline_fk:
                    definition += ", " + self.sql_create_inline_fk % {
                        "from_table": from_table,
                        "from_column": from_column,
                        "to_table": to_table,
                        "to_column": to_column,
                        "from_column_norm": self.quote_name(from_column),
                        "to_table_norm": self.quote_name(to_table),
                        "to_column_norm": self.quote_name(to_column),
                    }
                elif self.connection.features.supports_foreign_keys:
                    self.deferred_sql.append(
                        self._create_fk_sql(model, field,
                                            "_fk_%(to_table)s_%(to_column)s"))
            # Add the SQL to our big list
            column_sqls.append("%s %s" %
                               (self.quote_name(field.column), definition))
            # Create a unique constraint separately because Spanner doesn't
            # allow them inline on a column.
            if field.unique and not field.primary_key:
                self.deferred_sql.append(
                    self._create_unique_sql(model, [field.column]))

        # Add any unique_togethers (always deferred, as some fields might be
        # created afterwards, like geometry fields with some backends)
        for fields in model._meta.unique_together:
            columns = [model._meta.get_field(field).column for field in fields]
            self.deferred_sql.append(self._create_unique_sql(model, columns))
        constraints = [
            constraint.constraint_sql(model, self)
            for constraint in model._meta.constraints
        ]
        # Make the table
        sql = self.sql_create_table % {
            "table":
            self.quote_name(model._meta.db_table),
            "definition":
            ", ".join(constraint for constraint in (*column_sqls, *constraints)
                      if constraint),
            "primary_key":
            self.quote_name(model._meta.pk.column),
        }
        if model._meta.db_tablespace:
            tablespace_sql = self.connection.ops.tablespace_sql(
                model._meta.db_tablespace)
            if tablespace_sql:
                sql += " " + tablespace_sql
        # Prevent using [] as params, in the case a literal '%' is used in the
        # definition
        trace_attributes = {
            "model_name": self.quote_name(model._meta.db_table)
        }

        with trace_call(
                "CloudSpannerDjango.create_model",
                self.connection,
                trace_attributes,
        ):
            self.execute(sql, params or None)

        # Add any field index and index_together's (deferred as SQLite
        # _remake_table needs it)
        self.deferred_sql.extend(self._model_indexes_sql(model))

        # Make M2M tables
        for field in model._meta.local_many_to_many:
            if field.remote_field.through._meta.auto_created:
                self.create_model(field.remote_field.through)
Ejemplo n.º 7
0
 def _alter_field(
     self,
     model,
     old_field,
     new_field,
     old_type,
     new_type,
     old_db_params,
     new_db_params,
     strict=False,
 ):
     # Spanner requires dropping indexes before changing the nullability
     # of a column.
     nullability_changed = old_field.null != new_field.null
     if nullability_changed:
         index_names = self._constraint_names(model, [old_field.column],
                                              index=True)
         if index_names and not old_field.db_index:
             raise NotSupportedError(
                 "Changing nullability of a field with an index other than "
                 "Field(db_index=True) isn't yet supported.")
         if len(index_names) > 1:
             raise NotSupportedError(
                 "Changing nullability of a field with more than one "
                 "index isn't yet supported.")
         for index_name in index_names:
             trace_attributes = {
                 "model_name": self.quote_name(model._meta.db_table),
                 "alter_field": old_field.column,
                 "index_name": index_name,
             }
             with trace_call(
                     "CloudSpannerDjango.alter_field.delete_index",
                     self.connection,
                     trace_attributes,
             ):
                 self.execute(self._delete_index_sql(model, index_name))
     trace_attributes = {
         "model_name": self.quote_name(model._meta.db_table),
         "alter_field": old_field.column,
     }
     with trace_call(
             "CloudSpannerDjango.alter_field",
             self.connection,
             trace_attributes,
     ):
         super()._alter_field(
             model,
             old_field,
             new_field,
             old_type,
             new_type,
             old_db_params,
             new_db_params,
             strict=False,
         )
     # Recreate the index that was dropped earlier.
     if nullability_changed and new_field.db_index:
         trace_attributes = {
             "model_name": self.quote_name(model._meta.db_table),
             "alter_field": new_field.column,
         }
         with trace_call(
                 "CloudSpannerDjango.alter_field.recreate_index",
                 self.connection,
                 trace_attributes,
         ):
             self.execute(self._create_index_sql(model, fields=[new_field]))
Ejemplo n.º 8
0
 def test_no_trace_call(self):
     with _opentelemetry_tracing.trace_call(
             "Test", _make_connection()) as no_span:
         self.assertIsNone(no_span)