Skip to content

Commit c8e3617

Browse files
craig[bot]rafissRaduBerinde
committed
144955: logictest: add test cases for RLS interaction with triggers r=rafiss a=rafiss Epic: CRDB-48807 Release note: None 145288: storage: minor cleanups around pebble compaction concurrency r=RaduBerinde a=RaduBerinde #### storage: remove AdjustCompactionConcurrency This is not used. Epic: none Release note: None #### storage: remove unnecessary initialization in newPebble We now only call this function from Open and we always have options set. Epic: none Release note: None #### pebble: separate compaction concurrency override logic Separate the logic that allows temporarily overriding compaction concurrency. Epic: none Release note: None #### storage: cleanup default max concurrent compactions code Refactor this code so we statically initialize the default value. Epic: none Release note: None Co-authored-by: Rafi Shamim <[email protected]> Co-authored-by: Radu Berinde <[email protected]>
3 parents 44af9e0 + 9b699b9 + e8985d3 commit c8e3617

File tree

6 files changed

+358
-104
lines changed

6 files changed

+358
-104
lines changed

pkg/kv/kvserver/stores_server.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,19 +191,21 @@ func (is Server) ScanStorageInternalKeys(
191191
// SetCompactionConcurrency implements PerStoreServer. It changes the compaction
192192
// concurrency of a store. While SetCompactionConcurrency is safe for concurrent
193193
// use, it adds uncertainty about the compaction concurrency actually set on
194-
// the store. It also adds uncertainty about the compaction concurrency set on
195-
// the store once the request is cancelled.
194+
// the store. We do guarantee that once all SetCompactionConcurrency requests
195+
// are finished (cancelled), the override is removed and the original
196+
// concurrency is restored.
196197
func (is Server) SetCompactionConcurrency(
197198
ctx context.Context, req *CompactionConcurrencyRequest,
198199
) (*CompactionConcurrencyResponse, error) {
199200
resp := &CompactionConcurrencyResponse{}
200201
err := is.execStoreCommand(ctx, req.StoreRequestHeader,
201202
func(ctx context.Context, s *Store) error {
202-
prevConcurrency := s.TODOEngine().SetCompactionConcurrency(req.CompactionConcurrency)
203+
s.TODOEngine().SetCompactionConcurrency(req.CompactionConcurrency)
203204

204-
// Wait for cancellation, and once cancelled, reset the compaction concurrency.
205+
// Wait for cancellation, and once cancelled, reset the compaction
206+
// concurrency.
205207
<-ctx.Done()
206-
s.TODOEngine().SetCompactionConcurrency(prevConcurrency)
208+
s.TODOEngine().SetCompactionConcurrency(0)
207209
return nil
208210
})
209211
return resp, err

pkg/sql/logictest/testdata/logic_test/row_level_security

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4485,3 +4485,267 @@ statement ok
44854485
DROP ROLE parent_role
44864486

44874487
subtest end
4488+
4489+
subtest triggers_with_rls
4490+
4491+
statement ok
4492+
CREATE TABLE trigger_rls_table (id INT PRIMARY KEY, value INT, owner STRING);
4493+
4494+
statement ok
4495+
INSERT INTO trigger_rls_table VALUES (1, 100, 'alice'), (2, 200, 'bob'), (3, 300, 'alice'), (4, 400, 'bob');
4496+
4497+
statement ok
4498+
CREATE USER alice;
4499+
4500+
statement ok
4501+
CREATE USER bob;
4502+
4503+
statement ok
4504+
GRANT ALL ON trigger_rls_table TO alice, bob;
4505+
4506+
# Enable RLS on the table
4507+
statement ok
4508+
ALTER TABLE trigger_rls_table ENABLE ROW LEVEL SECURITY;
4509+
4510+
# Create a policy that only allows users to see their own data.
4511+
statement ok
4512+
CREATE POLICY owner_policy ON trigger_rls_table
4513+
USING (owner = current_user())
4514+
WITH CHECK (owner = current_user());
4515+
4516+
# Create a trigger function that logs changes to the table.
4517+
statement ok
4518+
CREATE FUNCTION log_value_changes() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4519+
BEGIN
4520+
IF TG_OP = 'UPDATE' THEN
4521+
RAISE NOTICE 'User % updated value for id % from % to %', current_user(), (NEW).id, (OLD).value, (NEW).value;
4522+
ELSIF TG_OP = 'INSERT' THEN
4523+
RAISE NOTICE 'User % inserted new row with id % and value %', current_user(), (NEW).id, (NEW).value;
4524+
ELSIF TG_OP = 'DELETE' THEN
4525+
RAISE NOTICE 'User % deleted row with id % and value %', current_user(), (OLD).id, (OLD).value;
4526+
END IF;
4527+
RETURN NEW;
4528+
END;
4529+
$$;
4530+
4531+
# Create a trigger function that enforces a business rule: value must be positive.
4532+
statement ok
4533+
CREATE FUNCTION enforce_positive_value() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4534+
BEGIN
4535+
IF (NEW).value <= 0 THEN
4536+
RAISE EXCEPTION 'Value must be positive';
4537+
END IF;
4538+
RETURN NEW;
4539+
END;
4540+
$$;
4541+
4542+
# Create a trigger that logs changes.
4543+
statement ok
4544+
CREATE TRIGGER log_changes
4545+
AFTER INSERT OR UPDATE OR DELETE ON trigger_rls_table
4546+
FOR EACH ROW
4547+
EXECUTE FUNCTION log_value_changes();
4548+
4549+
# Create a trigger that enforces the business rule.
4550+
statement ok
4551+
CREATE TRIGGER check_positive_value
4552+
BEFORE INSERT OR UPDATE ON trigger_rls_table
4553+
FOR EACH ROW
4554+
EXECUTE FUNCTION enforce_positive_value();
4555+
4556+
# Test as alice.
4557+
statement ok
4558+
SET ROLE alice;
4559+
4560+
# Alice should only see her own rows.
4561+
query IIT
4562+
SELECT * FROM trigger_rls_table ORDER BY id;
4563+
----
4564+
1 100 alice
4565+
3 300 alice
4566+
4567+
# Update a row that alice owns.
4568+
query T noticetrace
4569+
UPDATE trigger_rls_table SET value = 150 WHERE id = 1;
4570+
----
4571+
NOTICE: User alice updated value for id 1 from 100 to 150
4572+
4573+
# Try to update a row that bob owns (should not error, but should not affect any rows).
4574+
query T noticetrace
4575+
UPDATE trigger_rls_table SET value = 250 WHERE id = 2;
4576+
----
4577+
4578+
# Test negative value should trigger error.
4579+
statement error pq: Value must be positive
4580+
UPDATE trigger_rls_table SET value = -100 WHERE id = 1;
4581+
4582+
# Insert a new row that alice owns.
4583+
query T noticetrace
4584+
INSERT INTO trigger_rls_table VALUES (5, 500, 'alice');
4585+
----
4586+
NOTICE: User alice inserted new row with id 5 and value 500
4587+
4588+
# Try to insert a row that bob owns (should fail due to RLS policy).
4589+
statement error pq: new row violates row-level security policy for table "trigger_rls_table"
4590+
INSERT INTO trigger_rls_table VALUES (6, 600, 'bob');
4591+
4592+
# Delete a row that alice owns.
4593+
query T noticetrace
4594+
DELETE FROM trigger_rls_table WHERE id = 3;
4595+
----
4596+
NOTICE: User alice deleted row with id 3 and value 300
4597+
4598+
# Test as bob.
4599+
statement ok
4600+
SET ROLE bob;
4601+
4602+
# Bob should only see his own rows.
4603+
query IIT
4604+
SELECT * FROM trigger_rls_table ORDER BY id;
4605+
----
4606+
2 200 bob
4607+
4 400 bob
4608+
4609+
# Update a row that bob owns.
4610+
query T noticetrace
4611+
UPDATE trigger_rls_table SET value = 250 WHERE id = 2;
4612+
----
4613+
NOTICE: User bob updated value for id 2 from 200 to 250
4614+
4615+
# Try to update a row that alice owns (should not error, but should not affect any rows).
4616+
query T noticetrace
4617+
UPDATE trigger_rls_table SET value = 175 WHERE id = 1;
4618+
----
4619+
4620+
# Insert a new row that bob owns.
4621+
query T noticetrace
4622+
INSERT INTO trigger_rls_table VALUES (7, 700, 'bob');
4623+
----
4624+
NOTICE: User bob inserted new row with id 7 and value 700
4625+
4626+
# Test if bob can bypass RLS with a trigger function that modifies other rows.
4627+
statement ok
4628+
CREATE FUNCTION try_bypass_rls() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4629+
BEGIN
4630+
UPDATE trigger_rls_table SET value = 999 WHERE owner = 'alice';
4631+
RETURN NEW;
4632+
END;
4633+
$$;
4634+
4635+
statement ok
4636+
CREATE TRIGGER bypass_attempt
4637+
AFTER INSERT ON trigger_rls_table
4638+
FOR EACH ROW
4639+
EXECUTE FUNCTION try_bypass_rls();
4640+
4641+
# Try to trigger the bypass (this insert will succeed but the trigger's UPDATE should be limited by RLS).
4642+
statement ok
4643+
INSERT INTO trigger_rls_table VALUES (8, 800, 'bob');
4644+
4645+
# Check as root what happened.
4646+
statement ok
4647+
SET ROLE root;
4648+
4649+
# Alice's rows should not have been affected by bob's trigger.
4650+
query IIT
4651+
SELECT * FROM trigger_rls_table WHERE owner = 'alice' ORDER BY id;
4652+
----
4653+
1 150 alice
4654+
5 500 alice
4655+
4656+
# Now test a BEFORE trigger that modifies a column in a way that would violate an RLS policy.
4657+
statement ok
4658+
SET ROLE alice;
4659+
4660+
# Create a trigger function that tries to change the owner column to 'bob'.
4661+
statement ok
4662+
CREATE FUNCTION change_owner_to_bob() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4663+
BEGIN
4664+
NEW.owner = 'bob';
4665+
RETURN NEW;
4666+
END;
4667+
$$;
4668+
4669+
# Create a trigger that applies the function before insert.
4670+
statement ok
4671+
CREATE TRIGGER force_bob_ownership
4672+
BEFORE INSERT ON trigger_rls_table
4673+
FOR EACH ROW
4674+
EXECUTE FUNCTION change_owner_to_bob();
4675+
4676+
# Try to insert a row - this should fail because the trigger modifies
4677+
# the owner to 'bob' which violates alice's RLS policy.
4678+
statement error pq: new row violates row-level security policy for table "trigger_rls_table"
4679+
INSERT INTO trigger_rls_table VALUES (9, 900, 'alice');
4680+
4681+
# Now do the same with update.
4682+
statement ok
4683+
CREATE FUNCTION change_updated_owner_to_bob() RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
4684+
BEGIN
4685+
IF TG_OP = 'UPDATE' THEN
4686+
NEW.owner = 'bob';
4687+
END IF;
4688+
RETURN NEW;
4689+
END;
4690+
$$;
4691+
4692+
statement ok
4693+
CREATE TRIGGER force_update_bob_ownership
4694+
BEFORE UPDATE ON trigger_rls_table
4695+
FOR EACH ROW
4696+
EXECUTE FUNCTION change_updated_owner_to_bob();
4697+
4698+
# Try to update a row - this should fail because the trigger modifies
4699+
# the owner to 'bob' which violates alice's RLS policy.
4700+
statement error pq: new row violates row-level security policy for table "trigger_rls_table"
4701+
UPDATE trigger_rls_table SET value = 600 WHERE id = 5;
4702+
4703+
# Let's try with both bob and root to see how it works for them.
4704+
statement ok
4705+
SET ROLE bob;
4706+
4707+
# For bob, changing owner to bob is allowed by the policy.
4708+
query T noticetrace
4709+
INSERT INTO trigger_rls_table VALUES (10, 1000, 'alice');
4710+
----
4711+
NOTICE: User bob inserted new row with id 10 and value 1000
4712+
4713+
# Verify the owner was changed to bob by the trigger.
4714+
query IIT
4715+
SELECT * FROM trigger_rls_table WHERE id = 10;
4716+
----
4717+
10 1000 bob
4718+
4719+
# For completeness, let's see from the root perspective.
4720+
statement ok
4721+
SET ROLE root;
4722+
4723+
# Check all rows in the table to see what happened.
4724+
query IIT
4725+
SELECT * FROM trigger_rls_table ORDER BY id;
4726+
----
4727+
1 150 alice
4728+
2 250 bob
4729+
4 400 bob
4730+
5 500 alice
4731+
7 700 bob
4732+
8 800 bob
4733+
10 1000 bob
4734+
4735+
# Clean up the additional triggers and functions.
4736+
statement ok
4737+
DROP TRIGGER force_bob_ownership ON trigger_rls_table;
4738+
DROP TRIGGER force_update_bob_ownership ON trigger_rls_table;
4739+
DROP FUNCTION change_owner_to_bob;
4740+
DROP FUNCTION change_updated_owner_to_bob;
4741+
DROP TRIGGER log_changes ON trigger_rls_table;
4742+
DROP TRIGGER check_positive_value ON trigger_rls_table;
4743+
DROP TRIGGER bypass_attempt ON trigger_rls_table;
4744+
DROP FUNCTION log_value_changes;
4745+
DROP FUNCTION enforce_positive_value;
4746+
DROP FUNCTION try_bypass_rls;
4747+
DROP TABLE trigger_rls_table;
4748+
DROP USER alice;
4749+
DROP USER bob;
4750+
4751+
subtest end

pkg/storage/engine.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,13 +1102,9 @@ type Engine interface {
11021102
// version that it must maintain compatibility with.
11031103
SetMinVersion(version roachpb.Version) error
11041104

1105-
// SetCompactionConcurrency is used to set the engine's compaction
1106-
// concurrency. It returns the previous compaction concurrency.
1107-
SetCompactionConcurrency(n uint64) uint64
1108-
1109-
// AdjustCompactionConcurrency adjusts the compaction concurrency up or down by
1110-
// the passed delta, down to a minimum of 1.
1111-
AdjustCompactionConcurrency(delta int64) uint64
1105+
// SetCompactionConcurrency is used to override the engine's max compaction
1106+
// concurrency. A value of 0 removes any existing override.
1107+
SetCompactionConcurrency(n uint64)
11121108

11131109
// SetStoreID informs the engine of the store ID, once it is known.
11141110
// Used to show the store ID in logs and to initialize the shared object

pkg/storage/mvcc.go

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -101,46 +101,32 @@ var TargetBytesPerLockConflictError = settings.RegisterIntSetting(
101101
settings.WithName("storage.mvcc.target_bytes_per_lock_conflict_error"),
102102
)
103103

104-
// getMaxConcurrentCompactions wraps the maxConcurrentCompactions env var in a
105-
// func that may be installed on Options.MaxConcurrentCompactions. It also
106-
// imposes a floor on the max, so that an engine is always created with at least
107-
// 1 slot for a compactions.
108-
//
109-
// NB: This function inspects the environment every time it's called. This is
110-
// okay, because Engine construction in NewPebble will invoke it and store the
111-
// value on the Engine itself.
112-
func getMaxConcurrentCompactions() int {
113-
n := envutil.EnvOrDefaultInt(
114-
"COCKROACH_CONCURRENT_COMPACTIONS", func() int {
115-
// The old COCKROACH_ROCKSDB_CONCURRENCY environment variable was never
116-
// documented, but customers were told about it and use today in
117-
// production. We don't want to break them, so if the new env var
118-
// is unset but COCKROACH_ROCKSDB_CONCURRENCY is set, use the old env
119-
// var's value. This old env var has a wart in that it's expressed as a
120-
// number of concurrency slots to make available to both flushes and
121-
// compactions (a vestige of the corresponding RocksDB option's
122-
// mechanics). We need to adjust it to be in terms of just compaction
123-
// concurrency by subtracting the flushing routine's dedicated slot.
124-
//
125-
// TODO(jackson): Should envutil expose its `getEnv` internal func for
126-
// cases like this where we actually want to know whether it's present
127-
// or not; not just fallback to a default?
128-
if oldV := envutil.EnvOrDefaultInt("COCKROACH_ROCKSDB_CONCURRENCY", 0); oldV > 0 {
129-
return oldV - 1
130-
}
131-
132-
// By default use up to min(numCPU-1, 3) threads for background
133-
// compactions per store (reserving the final process for flushes).
134-
const max = 3
135-
if n := runtime.GOMAXPROCS(0); n-1 < max {
136-
return n - 1
137-
}
138-
return max
139-
}())
140-
if n < 1 {
141-
return 1
142-
}
143-
return n
104+
var defaultMaxConcurrentCompactions = getDefaultMaxConcurrentCompactions()
105+
106+
func getDefaultMaxConcurrentCompactions() int {
107+
if v := envutil.EnvOrDefaultInt("COCKROACH_CONCURRENT_COMPACTIONS", 0); v > 0 {
108+
return v
109+
}
110+
// The old COCKROACH_ROCKSDB_CONCURRENCY environment variable was never
111+
// documented, but customers were told about it and use today in
112+
// production. We don't want to break them, so if the new env var
113+
// is unset but COCKROACH_ROCKSDB_CONCURRENCY is set, use the old env
114+
// var's value. This old env var has a wart in that it's expressed as a
115+
// number of concurrency slots to make available to both flushes and
116+
// compactions (a vestige of the corresponding RocksDB option's
117+
// mechanics). We need to adjust it to be in terms of just compaction
118+
// concurrency by subtracting the flushing routine's dedicated slot.
119+
if oldV := envutil.EnvOrDefaultInt("COCKROACH_ROCKSDB_CONCURRENCY", 0); oldV > 0 {
120+
return max(oldV-1, 1)
121+
}
122+
123+
// By default use up to min(GOMAXPROCS-1, 3) threads for background
124+
// compactions per store (reserving the final process for flushes).
125+
const upperLimit = 3
126+
if n := runtime.GOMAXPROCS(0); n-1 < upperLimit {
127+
return max(n-1, 1)
128+
}
129+
return upperLimit
144130
}
145131

146132
// l0SubLevelCompactionConcurrency is the sub-level threshold at which to

0 commit comments

Comments
 (0)