Repository: sqlfluff/sqlfluff

Issue:
Inconsistent output depending on --processes flag when --ignore linting is used
### Search before asking

- [X] I searched the [issues](https://github.com/sqlfluff/sqlfluff/issues) and found no similar issues.


### What Happened

Depending on the value you set for the `--processes` flag when also using `--ignore linting`, different output with different exit codes are generated.

### Expected Behaviour

The same exit code should be generated, independently of the `--processes` flag. Furthermore, from https://docs.sqlfluff.com/en/stable/production.html#using-sqlfluff-on-a-whole-sql-codebase I would expect that exit codes should be either `0` or `65`, not `1`.

### Observed Behaviour

See the How to reproduce section.

### How to reproduce

Create a `test.sql` file with the following content:

```SQL
CREATE TABLE example (
    id TEXT DEFAULT 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In condimentum congue est, ac orci aliquam.' PRIMARY KEY
);
```

The line is too long according to SQLFluff, caused by the large default value, so let's see the the output of SQLFluff.

Running

```SHELL
sqlfluff fix --dialect postgres --ignore linting --processes 2
```

results in 

```
==== finding fixable violations ====
==== no fixable linting violations found ====                                                                                                                                                                      
All Finished 📜 🎉!
  [1 unfixable linting violations found]
```

with exit code `1`. Running the same with one process instead:

```SHELL
sqlfluff fix --dialect postgres --ignore linting --processes 1
```

results in

```
==== finding fixable violations ====
==== no fixable linting violations found ====                                                                                                                                                                      
All Finished 📜 🎉!
```

and exit code `0`

Same behaviour for `lint` and `format` commands.

### Dialect

Postgres

### Version

2.2.0, Python 3.10.6

### Configuration

None, it's all in the CLI flags.

### Are you willing to work on and submit a PR to address the issue?

- [ ] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://github.com/sqlfluff/sqlfluff/blob/main/CODE_OF_CONDUCT.md)



Discussion:
This is _very_ interesting! I'll pick this one up 👍 
It's worth calling out that I think part of the problem here is that the docs are also out of date - but you're still right that the return codes should be _the same_ regardless of the `processes` setting.

Patch:
diff --git a/src/sqlfluff/core/errors.py b/src/sqlfluff/core/errors.py
--- a/src/sqlfluff/core/errors.py
+++ b/src/sqlfluff/core/errors.py
@@ -47,9 +47,15 @@ def __init__(
             self.line_pos = line_pos
         super().__init__(self.desc())
 
+    def __eq__(self, other) -> bool:
+        """Errors compare equal if they are the same type and same content."""
+        if not isinstance(other, self.__class__):
+            return False
+        return self.__dict__ == other.__dict__
+
     def __reduce__(
         self,
-    ) -> Tuple[Type["SQLBaseError"], Tuple[Any, ...]]:  # pragma: no cover
+    ) -> Tuple[Type["SQLBaseError"], Tuple[Any, ...]]:
         """Prepare the SQLBaseError for pickling."""
         return type(self), (
             self.description,
@@ -169,6 +175,9 @@ def __init__(
         segment: Optional["BaseSegment"] = None,
         line_no: int = 0,
         line_pos: int = 0,
+        ignore: bool = False,
+        fatal: bool = False,
+        warning: Optional[bool] = None,
     ) -> None:
         # Store the segment on creation - we might need it later
         self.segment = segment
@@ -177,13 +186,24 @@ def __init__(
             pos=segment.pos_marker if segment else None,
             line_no=line_no,
             line_pos=line_pos,
+            ignore=ignore,
+            fatal=fatal,
+            warning=warning,
         )
 
     def __reduce__(
         self,
-    ) -> Tuple[Type["SQLParseError"], Tuple[Any, ...]]:  # pragma: no cover
+    ) -> Tuple[Type["SQLParseError"], Tuple[Any, ...]]:
         """Prepare the SQLParseError for pickling."""
-        return type(self), (self.description, self.segment, self.line_no, self.line_pos)
+        return type(self), (
+            self.description,
+            self.segment,
+            self.line_no,
+            self.line_pos,
+            self.ignore,
+            self.fatal,
+            self.warning,
+        )
 
 
 class SQLLintError(SQLBaseError):
@@ -208,20 +228,34 @@ def __init__(
         segment: "BaseSegment",
         rule: "BaseRule",
         fixes: Optional[List["LintFix"]] = None,
+        ignore: bool = False,
+        fatal: bool = False,
+        warning: Optional[bool] = None,
     ) -> None:
-        # Something about position, message and fix?
         self.segment = segment
         self.rule = rule
         self.fixes = fixes or []
         super().__init__(
-            description=description, pos=segment.pos_marker if segment else None
+            description=description,
+            pos=segment.pos_marker if segment else None,
+            ignore=ignore,
+            fatal=fatal,
+            warning=warning,
         )
 
     def __reduce__(
         self,
-    ) -> Tuple[Type["SQLLintError"], Tuple[Any, ...]]:  # pragma: no cover
+    ) -> Tuple[Type["SQLLintError"], Tuple[Any, ...]]:
         """Prepare the SQLLintError for pickling."""
-        return type(self), (self.description, self.segment, self.rule, self.fixes)
+        return type(self), (
+            self.description,
+            self.segment,
+            self.rule,
+            self.fixes,
+            self.ignore,
+            self.fatal,
+            self.warning,
+        )
 
     @property
     def fixable(self) -> bool:
diff --git a/src/sqlfluff/core/parser/markers.py b/src/sqlfluff/core/parser/markers.py
--- a/src/sqlfluff/core/parser/markers.py
+++ b/src/sqlfluff/core/parser/markers.py
@@ -62,6 +62,11 @@ def __ge__(self, other: "PositionMarker") -> bool:
     def __le__(self, other: "PositionMarker") -> bool:
         return self.working_loc <= other.working_loc  # pragma: no cover TODO?
 
+    def __eq__(self, other) -> bool:
+        if not isinstance(other, PositionMarker):
+            return False  # pragma: no cover
+        return self.working_loc == other.working_loc
+
     @property
     def working_loc(self) -> Tuple[int, int]:
         """Location tuple for the working position."""


Test patch:
diff --git a/test/cli/commands_test.py b/test/cli/commands_test.py
--- a/test/cli/commands_test.py
+++ b/test/cli/commands_test.py
@@ -379,6 +379,20 @@ def test__cli__command_render_stdin():
                 "test/fixtures/linter/operator_errors.sql",
             ],
         ),
+        # Check ignoring linting (multiprocess)
+        # https://github.com/sqlfluff/sqlfluff/issues/5066
+        (
+            lint,
+            [
+                "-n",
+                "--ignore",
+                "linting",
+                "-p",
+                "2",
+                "test/fixtures/linter/operator_errors.sql",
+                "test/fixtures/linter/comma_errors.sql",
+            ],
+        ),
         # Check linting works in specifying multiple rules
         (
             lint,
diff --git a/test/core/errors_test.py b/test/core/errors_test.py
new file mode 100644
--- /dev/null
+++ b/test/core/errors_test.py
@@ -0,0 +1,75 @@
+"""Tests pickling and unpickling of errors."""
+
+import pickle
+import pytest
+import copy
+
+from sqlfluff.core.parser import PositionMarker, RawSegment
+from sqlfluff.core.rules import BaseRule
+from sqlfluff.core.templaters import TemplatedFile
+
+from sqlfluff.core.errors import SQLBaseError, SQLLintError, SQLParseError, SQLLexError
+
+
+class Rule_T078(BaseRule):
+    """A dummy rule."""
+
+    groups = ("all",)
+
+    def _eval(self, context):
+        pass
+
+
+def assert_pickle_robust(err: SQLBaseError):
+    """Test that the class remains the same through copying and pickling."""
+    # First try copying (and make sure they still compare equal)
+    err_copy = copy.copy(err)
+    assert err_copy == err
+    # Then try picking (and make sure they also still compare equal)
+    pickled = pickle.dumps(err)
+    pickle_copy = pickle.loads(pickled)
+    assert pickle_copy == err
+
+
+@pytest.mark.parametrize(
+    "ignore",
+    [True, False],
+)
+def test__lex_error_pickle(ignore):
+    """Test lexing error pickling."""
+    template = TemplatedFile.from_string("foobar")
+    err = SQLLexError("Foo", pos=PositionMarker(slice(0, 6), slice(0, 6), template))
+    # Set ignore to true if configured.
+    # NOTE: This not copying was one of the reasons for this test.
+    err.ignore = ignore
+    assert_pickle_robust(err)
+
+
+@pytest.mark.parametrize(
+    "ignore",
+    [True, False],
+)
+def test__parse_error_pickle(ignore):
+    """Test parse error pickling."""
+    template = TemplatedFile.from_string("foobar")
+    segment = RawSegment("foobar", PositionMarker(slice(0, 6), slice(0, 6), template))
+    err = SQLParseError("Foo", segment=segment)
+    # Set ignore to true if configured.
+    # NOTE: This not copying was one of the reasons for this test.
+    err.ignore = ignore
+    assert_pickle_robust(err)
+
+
+@pytest.mark.parametrize(
+    "ignore",
+    [True, False],
+)
+def test__lint_error_pickle(ignore):
+    """Test lint error pickling."""
+    template = TemplatedFile.from_string("foobar")
+    segment = RawSegment("foobar", PositionMarker(slice(0, 6), slice(0, 6), template))
+    err = SQLLintError("Foo", segment=segment, rule=Rule_T078)
+    # Set ignore to true if configured.
+    # NOTE: This not copying was one of the reasons for this test.
+    err.ignore = ignore
+    assert_pickle_robust(err)