class TestFieldTypes(TestCase): fixtures = ['test_model'] @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_array(self): res = bulk_update(TestModel, [{'id': 1, 'array_field': [1]}, {'id': 2, 'array_field': [2]}, {'id': 3, 'array_field': [3]}, {'id': 4, 'array_field': []}]) self.assertEqual(4, res) for pk, name, array_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'array_field'): if pk in {1, 2, 3}: self.assertListEqual([pk], array_field) elif pk == 4: self.assertListEqual([], array_field) else: self.assertIsNone(array_field) self.assertEqual('test%d' % pk, name) @skipIf(not jsonb_available(), "JSONB type is available in Postgres 9.4+ and django 1.9+ only") def test_jsonb(self): res = bulk_update(TestModel, [{'id': 1, 'json_field': {'test': '1'}}, {'id': 2, 'json_field': {'test': '2'}}, {'id': 3, 'json_field': {'test': '3'}}, {'id': 4, 'json_field': {}}, {'id': 5, 'json_field': {'single': "'", "multi": '"'}}]) self.assertEqual(5, res) for pk, name, json_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'json_field'): if pk in {1, 2, 3}: self.assertDictEqual({'test': str(pk)}, json_field) elif pk == 4: self.assertDictEqual({}, json_field) elif pk == 5: self.assertDictEqual({'single': "'", "multi": '"'}, json_field) else: self.assertIsNone(json_field) self.assertEqual('test%d' % pk, name) @skipIf(not hstore_available(), "HStoreField is available in Django 1.8+") def test_hstore(self): res = bulk_update(TestModel, [{'id': 1, 'hstore_field': {'test': '1'}}, {'id': 2, 'hstore_field': {'test': '2'}}, {'id': 3, 'hstore_field': {'test': '3'}}, {'id': 4, 'hstore_field': {}}, {'id': 5, 'hstore_field': {'single': "'", "multi": '"'}}]) self.assertEqual(5, res) for item in TestModel.objects.all().order_by('id'): if item.pk in {1, 2, 3}: self.assertDictEqual({'test': str(item.pk)}, item.hstore_field) elif item.pk == 4: self.assertDictEqual({}, item.hstore_field) elif item.pk == 5: self.assertDictEqual({'single': "'", "multi": '"'}, item.hstore_field) else: self.assertIsNone(item.hstore_field) self.assertEqual('test%d' % item.pk, item.name)
'Meta': Meta, '__module__': __name__ } if array_available(): from django.contrib.postgres.fields import ArrayField model_attrs['array_field'] = ArrayField( models.IntegerField(null=True, blank=True)) model_attrs['big_array_field'] = ArrayField(models.BigIntegerField(), default=list) if hstore_available(): from django.contrib.postgres.fields import HStoreField model_attrs['hstore_field'] = HStoreField(null=True, blank=True) if jsonb_available(): JSONField = import_pg_field_or_dummy('JSONField', jsonb_available) model_attrs['json_field'] = JSONField(null=True, blank=True) TestModel = type('TestModel', (models.Model, ), model_attrs) class MetaWithSchema: db_table = '"appschema"."testmodel"' unique_together = ['id', 'name'] model_attrs_with_schema = { 'name': models.CharField(max_length=50, null=True, blank=True, default=''), 'int_field': models.IntegerField(null=True, blank=True), 'objects': BulkUpdateManager(),
class TestSetFunctions(TestCase): fixtures = ['test_model'] def test_incr(self): res = bulk_update_or_create(TestModel, [{ 'id': 1, 'int_field': 1 }, { 'id': 5, 'int_field': 5 }, { 'id': 11, 'int_field': 11 }], set_functions={'int_field': '+'}) self.assertEqual(3, res) for pk, name, int_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'int_field'): if pk in {1, 5}: self.assertEqual(2 * pk, int_field) else: self.assertEqual(pk, int_field) if pk != 11: self.assertEqual('test%d' % pk, name) else: self.assertEqual('', name) def test_concat_str(self): res = bulk_update_or_create(TestModel, [{ 'id': 1, 'name': 'bulk_update_1' }, { 'id': 5, 'name': 'bulk_update_5' }, { 'id': 11, 'name': 'bulk_update_11' }], set_functions={'name': '||'}) self.assertEqual(3, res) for pk, name, int_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'int_field'): if pk in {1, 5}: self.assertEqual('test%dbulk_update_%d' % (pk, pk), name) elif pk == 11: self.assertEqual('bulk_update_%d' % pk, name) else: self.assertEqual('test%d' % pk, name) if pk != 11: self.assertEqual(pk, int_field) else: self.assertIsNone(int_field) def _test_concat_array(self, iteration, res): self.assertEqual(4, res) for pk, name, array_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'array_field'): if pk in {1, 2, 11}: self.assertListEqual([pk] * iteration, array_field) elif pk == 4: self.assertListEqual([], array_field) else: self.assertIsNone(array_field) if pk != 11: self.assertEqual('test%d' % pk, name) else: self.assertEqual('', name) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_concat_array(self): for i in range(1, 5): res = bulk_update_or_create(TestModel, [{'id': 1, 'array_field': [1]}, {'id': 2, 'array_field': [2]}, {'id': 11, 'array_field': [11]}, {'id': 4, 'array_field': []}], set_functions={'array_field': '||'}) self._test_concat_array(i, res) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_concat_empty(self): res = bulk_update_or_create(TestModel, [{'id': 11, 'big_array_field': [2147483649]}], set_functions={'big_array_field': '||'}) self.assertEqual(1, res) self.assertListEqual([2147483649], TestModel.objects.get(pk=11).big_array_field) def _test_union_array(self, iteration, res): self.assertEqual(4, res) for pk, name, array_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'array_field'): if pk == 1: self.assertListEqual([pk], array_field) elif pk in {2, 11}: # Union doesn't save order, let's sort result array_field.sort() self.assertListEqual(list(range(1, iteration + 1)), array_field) elif pk == 4: self.assertListEqual([], array_field) else: self.assertIsNone(array_field) if pk != 11: self.assertEqual('test%d' % pk, name) else: self.assertEqual('', name) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_union_array(self): for i in range(1, 5): res = bulk_update_or_create(TestModel, [{'id': 1, 'array_field': [1]}, {'id': 2, 'array_field': [i]}, {'id': 11, 'array_field': [i]}, {'id': 4, 'array_field': []}], set_functions={'array_field': 'union'}) self._test_union_array(i, res) def _test_concat_dict(self, iteration, res, field_name, val_as_str=False): self.assertEqual(4, res) for pk, name, dict_field in TestModel.objects.all().order_by('id').values_list('id', 'name', field_name): if pk in {1, 2, 11}: # Note that JSON standard uses only strings as keys. So json.dumps will convert it expected = {str(i): str(pk) if val_as_str else pk for i in range(1, iteration + 1)} self.assertDictEqual(expected, dict_field) elif pk == 4: self.assertDictEqual({}, dict_field) else: self.assertIsNone(dict_field) if pk != 11: self.assertEqual('test%d' % pk, name) else: self.assertEqual('', name) @skipIf(not hstore_available(), "HStoreField is available in Django 1.8+") def test_concat_hstore(self): for i in range(1, 5): res = bulk_update_or_create(TestModel, [{'id': 1, 'hstore_field': {i: 1}}, {'id': 2, 'hstore_field': {i: 2}}, {'id': 11, 'hstore_field': {i: 11}}, {'id': 4, 'hstore_field': {}}], set_functions={'hstore_field': '||'}) self._test_concat_dict(i, res, 'hstore_field', val_as_str=True) @skipIf(not jsonb_available(), "JSONB type is available in Postgres 9.4+ and django 1.9+ only") def test_concat_jsonb(self): for i in range(1, 5): res = bulk_update_or_create(TestModel, [{'id': 1, 'json_field': {i: 1}}, {'id': 2, 'json_field': {i: 2}}, {'id': 11, 'json_field': {i: 11}}, {'id': 4, 'json_field': {}}], set_functions={'json_field': '||'}) self._test_concat_dict(i, res, 'json_field') def test_eq_not_null(self): res = bulk_update_or_create(TestModel, [{ 'id': 1, 'name': 'bulk_update_1' }, { 'id': 5, 'name': None }, { 'id': 11, 'name': None }], set_functions={'name': 'eq_not_null'}) self.assertEqual(3, res) # 9 from fixture + 1 created self.assertEqual(10, TestModel.objects.all().count()) for pk, name, int_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'int_field'): if pk in {1}: self.assertEqual('bulk_update_%d' % pk, name) elif pk == 11: # Default name, not None, look https://github.com/M1hacka/django-pg-bulk-update/issues/2 self.assertEqual('', name) else: self.assertEqual('test%d' % pk, name) if pk == 11: self.assertIsNone(int_field) else: self.assertEqual(pk, int_field) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_array_remove(self): def _test_array_remove(kwargs): res = bulk_update_or_create(TestModel, [{'id': 1, 'array_field': 1}, {'id': 2, 'array_field': 2}, {'id': 13, 'array_field': 13}], set_functions={'array_field': 'array_remove'}, **kwargs) self.assertEqual(3, res) for pk, array_field in TestModel.objects.filter(id__in=[1, 2, 13]).values_list('pk', 'array_field'): if pk == 1: self.assertEqual([2], array_field) elif pk == 2: self.assertEqual([1], array_field) elif pk == 13: self.assertEqual(None, array_field) TestModel.objects.all().update(array_field=[1, 2]) _test_array_remove({'key_is_unique': False}) # Force 3-step query TestModel.objects.filter(id=13).delete() TestModel.objects.all().update(array_field=[1, 2]) _test_array_remove({'key_is_unique': True})
'PASSWORD': '******', 'HOST': '127.0.0.1', 'PORT': '5432' } } LOGGING = { 'version': 1, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django-pg-bulk-update': { 'handlers': ['console'], 'level': 'DEBUG' } } } # DATABASES should be defined before this call from django_pg_bulk_update.compatibility import jsonb_available, array_available, hstore_available INSTALLED_APPS = [] if hstore_available() or jsonb_available() or array_available(): INSTALLED_APPS.append("django.contrib.postgres") INSTALLED_APPS.extend(["src", "tests"])
from django.db import migrations, models from django_pg_bulk_update.compatibility import jsonb_available, hstore_available, array_available, \ get_postgres_version, Postgres94MergeJSONBMigration, import_pg_field_or_dummy test_model_fields = [('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('int_field', models.IntegerField(null=True, blank=True)), ('name', models.CharField(max_length=50, null=True, blank=True))] if jsonb_available(): JSONField = import_pg_field_or_dummy('JSONField', jsonb_available) test_model_fields.append(('json_field', JSONField(null=True, blank=True))) if array_available(): from django.contrib.postgres.fields import ArrayField test_model_fields.append( ('array_field', ArrayField(models.IntegerField(), null=True, blank=True))) test_model_fields.append( ('big_array_field', ArrayField(models.BigIntegerField(), default=list))) if hstore_available(): from django.contrib.postgres.fields import HStoreField
class TestSetFunctions(TestCase): fixtures = ['test_model'] def test_incr(self): res = bulk_update(TestModel, [{ 'id': 1, 'int_field': 1 }, { 'id': 5, 'int_field': 5 }, { 'id': 8, 'int_field': 8 }], set_functions={'int_field': '+'}) self.assertEqual(3, res) for pk, name, int_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'int_field'): if pk in {1, 5, 8}: self.assertEqual(2 * pk, int_field) else: self.assertEqual(pk, int_field) self.assertEqual('test%d' % pk, name) def test_concat_str(self): res = bulk_update(TestModel, [{ 'id': 1, 'name': 'bulk_update_1' }, { 'id': 5, 'name': 'bulk_update_5' }, { 'id': 8, 'name': 'bulk_update_8' }], set_functions={'name': '||'}) self.assertEqual(3, res) for pk, name, int_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'int_field'): if pk in {1, 5, 8}: self.assertEqual('test%dbulk_update_%d' % (pk, pk), name) else: self.assertEqual('test%d' % pk, name) self.assertEqual(pk, int_field) def _test_concat_array(self, iteration, res): self.assertEqual(4, res) for pk, name, array_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'array_field'): if pk in {1, 2, 3}: self.assertListEqual([pk] * iteration, array_field) elif pk == 4: self.assertListEqual([], array_field) else: self.assertIsNone(array_field) self.assertEqual('test%d' % pk, name) def _test_union_array(self, iteration, res): self.assertEqual(4, res) for pk, name, array_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'array_field'): if pk in {1, 2}: self.assertListEqual([pk], array_field) elif pk == 3: # Order can be different here, so we sort the result array_field.sort() self.assertListEqual(list(range(1, iteration + 1)), array_field) elif pk == 4: self.assertListEqual([], array_field) else: self.assertIsNone(array_field) self.assertEqual('test%d' % pk, name) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_concat_array(self): for i in range(1, 5): res = bulk_update(TestModel, [{'id': 1, 'array_field': [1]}, {'id': 2, 'array_field': [2]}, {'id': 3, 'array_field': [3]}, {'id': 4, 'array_field': []}], set_functions={'array_field': '||'}) self._test_concat_array(i, res) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_union_array(self): for i in range(1, 5): res = bulk_update(TestModel, [{'id': 1, 'array_field': [1]}, {'id': 2, 'array_field': [2]}, {'id': 3, 'array_field': [i]}, {'id': 4, 'array_field': []}], set_functions={'array_field': 'union'}) self._test_union_array(i, res) def _test_concat_dict(self, iteration, res, field_name, val_as_str=False): self.assertEqual(4, res) for pk, name, dict_field in TestModel.objects.all().order_by('id').values_list('id', 'name', field_name): if pk in {1, 2, 3}: # Note that JSON standard uses only strings as keys. So json.dumps will convert it expected = {str(i): str(pk) if val_as_str else pk for i in range(1, iteration + 1)} self.assertDictEqual(expected, dict_field) elif pk == 4: self.assertDictEqual({}, dict_field) else: self.assertIsNone(dict_field) self.assertEqual('test%d' % pk, name) @skipIf(not hstore_available(), "HStoreField is available in Django 1.8+") def test_concat_hstore(self): for i in range(1, 5): res = bulk_update(TestModel, [{'id': 1, 'hstore_field': {i: 1}}, {'id': 2, 'hstore_field': {i: 2}}, {'id': 3, 'hstore_field': {i: 3}}, {'id': 4, 'hstore_field': {}}], set_functions={'hstore_field': '||'}) self._test_concat_dict(i, res, 'hstore_field', val_as_str=True) @skipIf(not jsonb_available(), "JSONB type is available in Postgres 9.4+ and django 1.9+ only") def test_concat_jsonb(self): for i in range(1, 5): res = bulk_update(TestModel, [{'id': 1, 'json_field': {i: 1}}, {'id': 2, 'json_field': {i: 2}}, {'id': 3, 'json_field': {i: 3}}, {'id': 4, 'json_field': {}}], set_functions={'json_field': '||'}) self._test_concat_dict(i, res, 'json_field') def test_eq_not_null(self): # Test, that NULL value in db will be NULL after update TestModel.objects.filter(pk=3).update(int_field=None) res = bulk_update(TestModel, [{'id': 1, 'int_field': 2}, {'id': 2, 'int_field': 3}, {'id': 3, 'int_field': None}, {'id': 4, 'int_field': None}], set_functions={'int_field': 'eq_not_null'}) self.assertEqual(4, res) for pk, name, int_field in TestModel.objects.all().order_by('id').values_list('id', 'name', 'int_field'): if pk in {1, 2}: self.assertEqual(pk, int_field - 1) elif pk == 3: self.assertIsNone(int_field) elif pk == 4: self.assertEqual(pk, int_field) else: self.assertEqual('test%d' % pk, name) self.assertEqual('test%d' % pk, name) @skipIf(not array_available(), "ArrayField is available in Django 1.8+") def test_array_remove(self): TestModel.objects.all().update(array_field=[1, 2, 2]) res = bulk_update(TestModel, [{'id': 1, 'array_field': 1}, {'id': 2, 'array_field': 2}, {'id': 3, 'array_field': 3}], set_functions={'array_field': 'array_remove'}) self.assertEqual(3, res) for pk, array_field in TestModel.objects.all().order_by('id').values_list('id', 'array_field'): if pk == 1: self.assertListEqual([2, 2], array_field) elif pk == 2: self.assertListEqual([1], array_field) elif pk == 3: self.assertListEqual([1, 2, 2], array_field)